• Aucun résultat trouvé

L’objet de nos travaux diffère de ceux qui ont été entrepris dans le courant des années ’90-2000 [16, 18,

15, 25]. En effet, ces travaux visent principalement à vérifier que le programme CP est correct alors que Sol- verCheck teste l’implémentation du solver utilisé pour résoudre celui-ci.

Bien que les propriétés à tester soient exprimées de manière formelle, SolverCheck est une bibliothèque de test et non pas un outil de vérification formelle, ce qui le rend plus simple à utiliser. En effet, ceux-ci ne supportent généralement pas toutes les construc- tions du langage utilisé, requièrent une guidance hu- maine [1, 4] ou ne sont pas applicables pour des pro-

grammes aussi larges et complexes que les solveurs CP actuels [20,19,21]. De même, le développement de sol- veurs CP certifiés formellement [17,12] ne permet pas – jusqu’à présent – d’égaler les solveurs CP actuels ; tant au niveau de l’efficacité (code suboptimal généré par une extraction de code OCaml depuis Coq [14]) qu’au niveau des fonctionnalités (ni contraintes ternaires, ni contraintes n-aires).

Récemment, la communauté SAT/SMT/ASP/QBF a entrepris de réaliser des travaux connexes aux nôtres [11,10,3,27]. Ceux-ci proposent aussi d’utiliser du fuzzing (génération d’entrées aléatoires) pour s’as- surer de la qualité de leurs outils. Cependant, contrai- rement à SolverCheck, ces travaux ne s’intéressent pas au cas d’un solveur CP. En particulier, ils ignorent les problématiques liées à la consistance (hybride ou non) des propagateurs. Or, comme cela a été dit précédem- ment, il s’agit d’un aspect essentiel du raisonnement et du développement d’un solveur CP.

Par ailleurs, Akgün et al. ont décrit lors de la der- nière conférence CP une approche visant à valider l’im- plémentation d’un solveur CP via des tests métamor- phiques [2]. L’objectif et l’intuition qui sous tendent leurs travaux sont les mêmes que pour SolverCheck. Dans les deux cas, il s’agit de valider l’implémentation des contraintes d’un solveur en comparant le résultat de deux implémentations distinctes d’un même propa- gateur. Les différences essentielles entre les deux ap- proches sont que : d’une part, leur méthode requiert que le testeur fournisse une table avec toutes les so- lutions de la contrainte alors que SolverCheck dérive automatiquement une implémentation naïve du propa- gateur. D’autre part, SolverCheck mets l’accent sur les consistances hybrides, ce qui n’est pas le cas de [2].

Enfin, il nous parait important de mentionner que Gecode [31] adopte une stratégie de test originale ; la- quelle lui permet de valider la correction et le niveau de consistance (AC, BC(D) ou BC(Z)) [7] imposé par ses propagateurs. Pour ce faire, il exploite habilement la propriété de monotonicité-faible [30] des propagateurs et le fait que toutes les valeurs d’un domaine filtré ont un (bound) support. Cette approche, bien qu’élégante et efficace, ne permet pas de tester la consistance d’un propagateur si celui-ci impose une consistance hybride (comme c’est le cas de la contrainte element [32]).

4 SolverCheck

Dans cet article, nous aborderons le langage fourni par SolverCheck au travers d’un exemple issu de la suite de tests que nous avons créé pour JaCoP.

Listing 1 – Exemple : LexOrder(ď) doit être GAC.

1 @Test

2 public void statelessLexLE () { 3 assertThat (

4 forAll ( listOf ("x", jDom ())). assertThat (x -> 5 forAll ( listOf ("y", jDom ())). assertThat (y ->

6 a( statelessJacopLexOrder (false)) 7 . isEquivalentTo (

8 arcConsistent ( lexLE (x. size () , y. size ()))) 9 . forAnyPartialAssignment (). with (x). then (y)))); 10 }

11

12 // Generer un domaine acceptable par JaCoP 13 public GenDomainBuilder jDom () {

14 return domain (). withValuesBetween (

15 IntDomain .MinInt , 16 IntDomain . MaxInt );

17 }

18 // Discriminer les solutions

19 public Checker lexLE (int x_sz , int y_sz ) { 20 return assignment -> {

21 var xs = assignment . subList (0, x_sz );

22 var ys = assignment . subList (x_sz , x_sz + y_sz ); 23 for(int i=0; i < min(x_sz , y_sz ); i++) { 24 if (xs.get(i) < ys.get(i)) return true; 25 if (xs.get(i) > ys.get(i)) return false; 26 }

27 return x_sz <= y_sz ; 28 };

29 }

4.1 Test déclaratif

Le code de test donné dans le listing-1 illustre l’as- pect déclaratif de SolverCheck. La propriété testée (lignes 3-9) stipule qu’étant donné deux listes x et y de variables dont le domaine n’est contraint que par la plage des valeurs acceptées par JaCoP (lignes 14-16) ; le filtrage des domaines obtenu par l’implémentation concrète est égal à ce qui serait attendu en théorie pour une contrainte et un niveau de consistance donné.

4.2 Extensible

Malgré son apparente simplicité, l’exemple repro- duit dans le listing-1 illustre bien la flexibilité offerte par SolverCheck. En effet, les lignes 19-27 montrent comment étendre les capacités de SolverCheck pour qu’il puisse tester de façon automatique des proprié- tés portant sur des contraintes qui lui sont a priori inconnues1. Il suffit pour ce faire de définir un nou-

veau Checker, c’est à dire, un prédicat portant sur les assignations complètes ; lequel est vrai ssi l’assignation complète satisfait la contrainte voulue.

4.3 Consistance

De la même façon, il est possible de paramétrer le niveau de consistance utilisé pour tester le propaga- teur. Pour ce faire, afin de modifier la propriété expri- mée dans l’exemple du listing-1 de sorte qu’elle spé- cifie que le niveau de filtrage du propagateur testé soit BC(Z) plutôt qu’AC, il suffirait de remplacer la ligne 8 par boundZConsistent(lexLE(x.size(), y.size())). De plus, étant donné que le niveau de filtrage implémenté dans les contraintes offertes pas les solveurs ne correspondent pas toujours à la dé- finition d’une des consistances "pures" ; SolverCheck 1. La bibliothèque fournit un certain nombre de checkers pour les contraintes les plus usuelles (alldiff, element, gcc, etc...).

permet de spécifier qu’un filtre peut être plus fort (isStrongerThan()), plus faible (isWeakerThan()) ou équivalent (isEquivalentTo()) à niveau de consis- tance donné. Ceci est illustré par la ligne 7 de notre exemple. Cependant, on peut considérer qu’il n’est pas suffisant de pouvoir dire qu’un filtre est plus fort ou plus faible qu’une consistance donnée. C’est pourquoi SolverCheck permet en outre de définir de nouvelles consistances hybrides. L’exemple du listing-2 illustre la façon de spécifier la consistance hybride exacte d’un propagateur. Cet exemple nous montre que la contrainte elementpA, x, yq ” Arxs “ y de MiniCP dans laquelle A est un tableau d’entier et x, y deux variables, impose que chaque valeur du domaine de x doit avoir un bound-support en y alors que seules les bornes sup. et inf. du domaine de y doivent avoir un support en x. De plus, cet exemple montre aussi (ligne 3) comment donner une limite de temps à SolverCheck pour l’exécution de ses tests.

Listing 2 – A[x] = y a une consistance hybride

1 @Test

2 public void elementIsHybridConsistent () { 3 given ( TIMELIMIT , TimeUnit . SECONDS ). assertThat ( 4 forAll ( listOf ("A", integer ())). assertThat (A -> 5 forAll ( domain ("x")). assertThat (x ->

6 forAll ( domain ("y")). assertThat (y -> 7 a( minicpElement1D (A))

8 . isEquivalentTo ( hybrid ( element (A),

9 rangeDomain () ,

10 bcDDomain ()))

11 . forAnyPartialAssignment (). with (x). then (y) 12 ))));

13 }

4.4 Test dynamique

Étant donné qu’un nombre non-négligeable de contraintes implémentent une propagation incrémen- tale en utilisant des structures à restauration d’état, il est nécessaire de s’assurer du bon fonctionnement du propagateur à tout moment de la recherche. En parti- culier, il faut s’assurer que les propriétés qui sont énon- cées soient valides même après plusieurs affectations et restaurations d’état. C’est pourquoi SolverCheck pro- pose un mode de test dynamique en plus des tests sta-

tiques qui ont été présentés jusqu’à maintenant.

Comme dans le cas d’un test statique, lorsque Sol- verCheck teste dynamiquement une propriété, il com- mence par générer un input aléatoire. Cependant, contrairement au cas statique, il considère cet input comme la racine d’un arbre de recherche et réalise des plongées pour explorer une fraction de celui-ci. A chaque noeud de l’arbre, le système vérifie que la propriété testée est bien maintenue ; sans quoi une trace menant à un état dans lequel la propriété est violée est rapportée à l’utilisateur. Lorsqu’une feuille de l’arbre est rencontrée, le système décide de remon- ter jusqu’à un noeud (choisi arbitrairement sur la der- nière branche explorée) avant d’entamer une nouvelle plongée.

Testé comme jamais... par Xavier Gillard, Pierre Schaus

En pratique, pour activer ce mode de test, il suf- fit à un utilisateur de fournir un StatefulFilter (dont l’interface est reproduite par le listing-3) plutôt qu’un Filter et d’utiliser le mot clé stateful pour enca- drer la déclaration du filtrage attendu. Concrètement, pour adapter l’exemple du listing-1, il suffirait de mo- difier la ligne 8 pour qu’elle ait la forme suivante : stateful(arcConsistent(...)))).

Listing 3 – Tests dynamiques : Interface StatefulFilter

1 public interface StatefulFilter {

2 void setup ( PartialAssignment initialDomains ); 3 void pushState ();

4 void popState ();

5 void branchOn (int var , Operator op , int val);

6 PartialAssignment currentState (); 7 }

5 Évaluation

Afin d’évaluer la capacité de SolverCheck à identifier des problèmes dans l’implémentation de contraintes par des solveurs réels, nous avons choisi d’utiliser notre outil pour tester les contraintes de quatre solveurs ba- sés sur la machine virtuelle Java ; à savoir : AbsCon[24], Choco[29], JaCoP[22] et MiniCP[23]. Ceux-ci ont été choisis d’une part, parce qu’ils tournent sur la JVM qui est la plate-forme cible de SolverCheck et d’autre part parce qu’ils ont été développés avec soin par des experts du domaine2. Parmi le large éventail de

contraintes possibles, nous en avons sélectionné six qui nous semblaient être représentatives du type de contraintes généralement disponibles dans un solveur CP.

La table-1résume les résultats observés lors de cette recherche de bugs. Comme on peut le voir, Solver- Check a été capable d’identifier des problèmes dans chacun des solveurs testés et cela pour pratiquement chacune des contraintes considérées. Et cela en dé- pit du fait que ces contraintes avaient déjà été tes- tées par les auteurs de ces solveurs. La nature des problèmes identifiés est, elle aussi, très variable : la stratégie de génération d’inputs pseudo-aléatoires biai- sée pour générer des valeurs extrêmes a naturellement permis la mise en évidence d’un certain nombre de problèmes d’over- et under-flows. Ceux-ci sont souvent contre-intuitifs pour un être humain et peuvent avoir un impact désastreux sur le solveur considéré : crash, boucle infinie et décision incorrecte de la contrainte sont quelques exemples qui ont été observés. Les pro- blèmes de dépassement sur les entiers finis ne sont tou- tefois pas les seuls problèmes à avoir été identifiés par notre outil. Parmi ceux-ci, on compte évidemment les problèmes de consistance plus faible qu’annoncée et 2. D’autres choix auraient bien entendu été possibles. Par exemple, il aurait tout à fait été envisageable d’intégrer OS- caR [28] à la liste des solveurs testés malgré le fait qu’il soit écrit en Scala et non pas en Java.

des erreurs de programmation (off-by-one, erreurs de cast, etc..). Mais on observe aussi d’autres types de problèmes tels que des erreurs logiques, des fuites mé- moires et des désaccords entre le comportement réel et documenté d’un propagateur.

Table 1 – Problèmes trouvés lors de l’évaluation

Solver Alldiff Element Table Sum GCC Lex

AbsCon oui oui oui oui oui oui

Choco oui oui oui oui non oui

JaCoP non oui non non non oui

MiniCP oui non oui oui N/A N/A

6 Conclusions

Dans cet article, nous avons présenté SolverCheck, une bibliothèque de test par propriété spécifiquement conçue pour évaluer la qualité des solveurs CP. Au travers d’un exemple simple, nous avons mis en évi- dence le caractère déclaratif des propriétés énoncées via le DSL de SolverCheck. Toujours sur base du même exemple, nous avons montré que la bibliothèque que nous proposons est extensible et peut être facilement complétée de sorte à traiter des contraintes qui n’ont pas été initialement été prévues. Nous avons aussi mon- tré que le langage de SolverCheck était suffisamment flexible que pour permettre de spécifier de manière as- sez fine la consistance attendue d’un propagateur ; ce qui facilite le raisonnement à propos de ce dernier. En- fin, nous avons montré que SolverCheck permet non seulement de réaliser des tests statiques de contraintes, mais aussi des tests dynamiques de celles-ci. Cela, afin de rendre compte de l’aspect incrémentiel inhérent à certains algorithmes efficaces. Enfin, l’évaluation de notre outil sur des contraintes et solveurs réels a per- mis de montrer la redoutable efficacité de celui-ci pour identifier et corriger des problèmes non triviaux dans les solveurs. De ce fait, nous pensons qu’une adoption plus large de SolverCheck serait bénéfique à la commu- nauté dans son ensemble, car cela permettrait d’aug- menter la qualité du code des solveurs tout en libérant du temps pour les développeurs qui devraient passer moins de temps à imaginer et copier-coller des cas de test. A fortiori puisque SolverCheck s’utilise facilement au travers de tests JUnit [5]/TestNG [8] et peut donc sans problème s’intégrer à un système d’intégration continue.

Nous envisageons plusieurs extensions à ces travaux. Entre autre, étendre SolverCheck de sorte qu’il génère et interprète des fragments de code XCSP3 [9] ou Mi- niZinc [26] afin de s’affranchir du cadre de la JVM et ainsi tester des solveurs qui ne tournent pas sur cette plateforme. D’autres options incluent le microbench- marking et test de contraintes de scheduling.

Références

[1] Wolfgang Ahrendt, Bernhard Beckert, Ri- chard Bubel, Reiner Hähnle, Peter H Schmitt et Mattias Ulbrich : Deductive Software Verification–The KeY Book : From Theory to

Practice, volume 10001. Springer, 2016.

[2] Özgür Akgün, Ian P Gent, Christopher Jeffer- son, Ian Miguel et Peter Nightingale : Meta- morphic testing of constraint solvers. In Inter-

national Conference on Principles and Practice of Constraint Programming, pages 727–736. Sprin-

ger, 2018.

[3] Cyrille Artho, Armin Biere et Martina Seidl : Model-based testing for verification back-ends. In

International Conference on Tests and Proofs,

pages 39–55. Springer, 2013.

[4] John Barnes : SPARK : The Proven Approach

to High Integrity Software. Altran Praxis, 2012.

[5] Kent Beck et Erich Gamma : Test infected : Programmers love writing tests. Java Report, 3(7) :37–50, 1998.

[6] Nicolas Beldiceanu, Mats Carlsson et Jean- Xavier Rampon : Global constraint catalog, 2010. [7] Christian Bessiere : Constraint propagation. In

Foundations of Artificial Intelligence, volume 2,

pages 29–83. Elsevier, 2006.

[8] Cedric Beust et Alexandru Popescu : Testng : Testing, the next generation, 2007.

[9] Frédéric Boussemart, Christophe Lecoutre et Cédric Piette : Xcsp3 : An integrated format for benchmarking combinatorial constrained pro- blems. arXiv preprint arXiv :1611.03398, 2016. [10] Robert Brummayer et Matti Järvisalo : Tes-

ting and debugging techniques for answer set sol- ver development. Theory and Practice of Logic

Programming, 10(4-6) :741–758, 2010.

[11] Robert Brummayer, Florian Lonsing et Armin Biere : Automated testing and debugging of sat and qbf solvers. In International Conference on

Theory and Applications of Satisfiability Testing,

pages 44–57. Springer, 2010.

[12] Matthieu Carlier, Catherine Dubois et Arnaud Gotlieb : A certified constraint solver over finite domains. In International Symposium on Formal

Methods, pages 116–131. Springer, 2012.

[13] Koen Claessen et John Hughes : Quickcheck : a lightweight tool for random testing of haskell pro- grams. Acm sigplan notices, 46(4) :53–64, 2011. [14] The coq development team : The coq proof assis-

tant, En ligne : 10 mai 2019 (https://coq.inria. fr/).

[15] Michael Dahmen : A debugger for constraints in prolog. 1991.

[16] Romuald Debruyune, Jean-Daniel Fekete, Na- rendra Jussien et Mohammad Ghoniem : Propo- sition de format concret pour des traces générées par des solveurs de contraintes réalisation rntl oa- dymppac 2.2. 2.1. 2001.

[17] Catherine Dubois et Arnaud Gotlieb : Solveurs cp (fd) vérifiés formellement. In Neuvi ? mes

Journ ? es Francophones de Programmation par Contraintes (JFPC 2013), pages 115–118, 2013.

[18] Mireille Ducassé : Opium+, a meta-debugger for prolog. In European Conference on Artificail

Intelligence, 1988.

[19] Jean-Christophe Filliâtre et Claude Marché : The why/krakatoa/caduceus platform for deduc- tive program verification. In International Confe-

rence on Computer Aided Verification, pages 173–

177. Springer, 2007.

[20] Jean-Christophe Filliâtre et Andrei Paske- vich : Why3 – Where Programs Meet Provers.

In ESOP’13 22nd European Symposium on Pro- gramming, volume 7792 de LNCS, Rome, Italy,

mars 2013. Springer.

[21] Florent Kirchner, Nikolai Kosmatov, Virgile Prevosto, Julien Signoles et Boris Yako- bowski : Frama-c : A software analysis perspec- tive. Formal Aspects of Computing, 27(3) :573– 609, 2015.

[22] Krzysztof Kuchcinski et Radoslaw Szymanek : Jacop-java constraint programming solver. In CP

Solvers : Modeling, Applications, Integration, and Standardization, co-located with the 19th Interna- tional Conference on Principles and Practice of Constraint Programming, 2013.

[23] Laurent Michel, Pierre Schaus, Pascal Van Hentenryck : MiniCP : A lightweight sol- ver for constraint programming, 2018. Available from https://minicp.bitbucket.io.

[24] Christophe Lecoutre et Sebastien Tabary : Abscon 112 : towards more robustness. In 3rd International Constraint Solver Competition (CSC’08), pages 41–48, 2008.

[25] Micha Meier : Debugging constraint programs.

In International Conference on Principles and Practice of Constraint Programming, pages 204–

221. Springer, 1995.

[26] Nicholas Nethercote, Peter J Stuckey, Ralph Becket, Sebastian Brand, Gregory J Duck et Guido Tack : Minizinc : Towards a standard cp modelling language. In International Conference

on Principles and Practice of Constraint Program- ming, pages 529–543. Springer, 2007.

[27] Aina Niemetz, Mathias Preiner et Armin Biere : Model-based api testing for smt sol- vers. In Proceedings of the 15th International

Testé comme jamais... par Xavier Gillard, Pierre Schaus

Workshop on Satisfiability Modulo Theories, SMT,

page 10, 2017.

[28] OscaR Team : OscaR : Scala

in OR, 2012. Available from

https://bitbucket.org/oscarlib/oscar. [29] Charles Prud’homme, Jean-Guillaume Fages et

Xavier Lorca : Choco Documentation. TASC - LS2N CNRS UMR 6241, COSLING S.A.S., 2017. [30] Christian Schulte et Guido Tack : Weakly mo- notonic propagators. In Ian P. Gent, éditeur :

Principles and Practice of Constraint Program- ming - CP 2009, volume 5732 de Lecture Notes in Computer Science, pages 723–730, Berlin, Hei-

delberg, septembre 2009. Springer.

[31] Christian Schulte, Guido Tack et Mikael Z. La- gerkvist : Modeling. In Christian Schulte, Guido Tack et Mikael Z. Lagerkvist, éditeurs :

Modeling and Programming with Gecode. 2018.

Corresponds to Gecode 6.1.0.

[32] Pascal Van Hentenryck et Jean-Philippe Ca- rillon : Generality versus specificity : An expe- rience with ai and or techniques.

Actes JFPC 2019

Outline

Documents relatifs