2017-02-26 2 views
0

La fonction process-async testée dans le cadre midje produit des résultats incohérents. La plupart du temps, il vérifie comme prévu, mais de temps en temps, il lit out.json à son état initial (""). Je compte sur la fonction async-blocker pour attendre process-async avant de vérifier.Résultats incohérents lors du test d'une fonction de traitement chan

Quel est le problème avec mon approche?

(require '[[test-with-files.core :refer [with-files public-dir]]) 

(defn async-blocker [fun & args] 
    (let [chan-test (chan)] 
    (go (>! chan-test (apply fun args))) 
    (<!! chan-test))) 

(defn process-async 
    [channel func] 
    (go-loop [] 
    (when-let [response (<! channel)] 
     (func response) 
     (recur)))) 

(with-files [["/out.json" ""]] 
    (facts "About `process-async" 
      (let [channel (chan) 
       file (io/resource (str public-dir "/out.json")) 
       write #(spit file (str % "\n") :append true)] 
      (doseq [m ["m1" "m2"]] (>!! channel m)) 
      (async-blocker process-async channel write) 
      (clojure.string/split-lines (slurp file)) => (just ["m1" "m2"] :in-any-order) 
      ) 
      ) 
    ) 

Répondre

1

Le problème est que process-async retourne immédiatement avec « [...] un canal qui recevra le résultat du corps lorsque terminé » (depuis go-loop est juste du sucre syntaxique pour (go (loop ...)) et go retourne immédiatement).

Cela signifie que le blocage <!! dans async-blocker aura une valeur presque immédiatement et l'ordre dans lequel les blocs de goprocess-async et async-blocker sont exécutées est indéterminée. Il se peut que la plupart du temps, le bloc process-async s'exécute en premier, car il est créé en premier, mais ce n'est pas vraiment une garantie dans un contexte concurrent.

Selon la documentation de <!! il "Renvoie zéro s'il est fermé, bloquera si rien n'est disponible." Cela signifie que si vous pouvez supposer que la valeur de retour de (apply fun args) est un canal retourné par go, vous devriez être en mesure de bloquer à l'aide <!! de la manière suivante:

(defn async-blocker [fun & args] 
    (<!! (apply fun args))) 

Cela débloquera une fois il y a une valeur dans le canal (c'est-à-dire la valeur de retour du bloc go).

Il existe d'autres options pour attendre le résultat d'un autre bloc go. Vous pouvez par exemple fournir l'chan-test d'origine comme argument à fun et puis put une valeur dans chan-test lorsque le bloc go créé dans fun se termine. Mais je pense, vu le code que vous avez montré, que d'autres approches pourraient être inutilement plus compliquées.