Ce didacticiel est destiné aux utilisateurs maîtrisant la programmation en C ou C++, ainsi que l'architecture de la Simple Virtual Machine.
Dans ce didacticiel, vous allez apprendre à ajouter des tests aux extensions.
Le temps de lecture de ce didacticiel est estimé à 15 minutes.
Pour commencer, créez le canevas de l'extension dans le fichier tests.svm_plugin en utilisant ce code :
PLUGIN test
DEFINE
INSTRUCTION test.instruction INT INT -> INT
%{
auto l = ARGV_VALUE(0,integer);
auto r = ARGV_VALUE(1,integer);
if(r==0)
{
ERROR_INTERNAL(NUMERIC,"Division by zero.");
}
return NEW_VALUE(integer,l/r);
%}
Lorsqu'une extension est destinée à être publiée à large échelle, il peut être important d'ajouter des tests pour vérifier le bon fonctionnement de l'extension dans le temps.
Les tests s'écrivent dans la partie générale de l'extension.
Modifiez le code de l'extension :
PLUGIN test
test: "Basic"
%{
PLUGIN "svmint.so"
PROCESS "test"
CODE "main" INLINE
:memory INT/i, BLN/b
:test.instruction 12 3 -> &i
:int.cmp @&i = 4 -> &b
:shutdown 1 :unless @&b TRUE
:test.instruction 12 5 -> &i
:int.cmp @&i = 2 -> &b
:shutdown 1 :unless @&b TRUE
:test.instruction 12 30 -> &i
:int.cmp @&i = 0 -> &b
:shutdown 1 :unless @&b TRUE
:test.instruction 12 -3 -> &i
:int.cmp @&i = -4 -> &b
:shutdown 1 :unless @&b TRUE
:shutdown 0
END
END
%}
test: "Divisionbyzero"
%{
-- 42 "is the answer"
%}
%{
PROCESS "test"
CODE "main" INLINE
:interruption NUMERIC trap
:memory INT/i
:test.instruction 12 0 -> &i
:shutdown 1
:label trap
:shutdown 2 :when &i INITIALISED
:shutdown 0
END
END
%}
DEFINE
INSTRUCTION test.instruction INT INT -> INT
%{
auto l = ARGV_VALUE(0,integer);
auto r = ARGV_VALUE(1,integer);
if(r==0)
{
ERROR_INTERNAL(NUMERIC,"Division by zero.");
}
return NEW_VALUE(integer,l/r);
%}
Générez et compilez l'extension.
Chaque test comporte un titre, également utilisé comme nom de fichier pour contenir le test, un bloc facultatif contenant les options et arguments à passer à l'extension, et un bloc de code en langage machine. Dans ce code, l'invocation de la machine et l'inclusion de l'extension à tester est automatique.
Lorsque la machine renvoie un code de retour :
Parfois, les tests doivent manipuler des éléments du système tels que des fichiers. Il existe des directives particulières pour créer et détruire ces éléments.
Modifiez le code de l'extension :
PLUGIN test
test: initialisation
%{
cat > input_file << EOM
12
4
EOM
%}
test: finalisation
%{
rm input_file
%}
test: "Basic"
%{
PLUGIN "svmint.so"
PROCESS "test"
CODE "main" INLINE
:memory INT/i, BLN/b
:test.instruction 12 3 -> &i
:int.cmp @&i = 4 -> &b
:shutdown 1 :unless @&b TRUE
:test.instruction 12 5 -> &i
:int.cmp @&i = 2 -> &b
:shutdown 1 :unless @&b TRUE
:test.instruction 12 30 -> &i
:int.cmp @&i = 0 -> &b
:shutdown 1 :unless @&b TRUE
:test.instruction 12 -3 -> &i
:int.cmp @&i = -4 -> &b
:shutdown 1 :unless @&b TRUE
:shutdown 0
END
END
%}
test: "Divisionbyzero"
%{
-- 42 "is the answer"
%}
%{
PROCESS "test"
CODE "main" INLINE
:interruption NUMERIC trap
:memory INT/i
:test.instruction 12 0 -> &i
:shutdown 1
:label trap
:shutdown 2 :when &i INITIALISED
:shutdown 0
END
END
%}
test: "File"
%{
PLUGIN "svmcom.so"
PLUGIN "svmstr.so"
PLUGIN "svmint.so"
PROCESS "test"
CODE "main" INLINE
:memory com.device/f, STR/s, INT/i, INT*2/r, BLN/b
:com.open com.file < "input_file" -> &f
0 -> &i
:label input
:com.read @&f com.line -> &s
:str.replace @&s 1 CONST str.pattern "\n" => ""
:int.parse @&s -> (r/@&i)
:shift &i
:goto input :when @&i IN r
:test.instruction @(r/0) @(r/1) -> &i
:int.cmp @&i = 3 -> &b
:shutdown 1 :unless @&b TRUE
END
END
%}
DEFINE
INSTRUCTION test.instruction INT INT -> INT
%{
auto l = ARGV_VALUE(0,integer);
auto r = ARGV_VALUE(1,integer);
if(r==0)
{
ERROR_INTERNAL(NUMERIC,"Division by zero.");
}
return NEW_VALUE(integer,l/r);
%}
Générez et compilez l'extension.
Trois tests ont été ajoutés :
initialisation
. Ce test spécial indique le script shell à exécuter avant de jouer les tests. C'est ce script qui va créer le fichier d'entrée.finalisation
. Ce test spécial indique le script shell à exécuter après avoir joué les tests. C'est ce script qui va supprimer le fichier d'entrée.test: "nom_test"
suivi d'un bloc de code en langage machine.test
contient deux blocs, le premier contient les options et arguments de l'extension testée, et le second contient le test.initialisation
, le contenu est un script shell d'initialisation de l'environnement de test.finalisation
, le contenu est un script shell de vérification finale et de nettoyage de l'environnement de test.Pour lancer les tests apres une compilation locale, il suffit de lancer la commande :
./plugin_install -l check
[...]
Test suite:
init: OK
Basic.svm: OK
Divisionbyzero.svm: OK
File.svm: OK
fini: OK
Result: 3/3 passed.
Chaque test est exécuté, puis son résultat est affiché. A la fin de tous les tests, un résumé de l'état des tests est donné.
Pour lancer les tests avec Autotools, il faut d'abord configurer et compiler l'extension. Ensuite, la commande make check
jouera les tests :
./plugin_install -c
[...]
./configure
[...]
make
[...]
make check
[...]
PASS: init.sh
PASS: Basic.svm
PASS: Divisionbyzero.svm
PASS: File.svm
PASS: fini.sh
============================================================================
Testsuite summary for svmplugintest 0.0
============================================================================
# TOTAL: 5
# PASS: 5
# SKIP: 0
# XFAIL: 0
# FAIL: 0
# XPASS: 0
# ERROR: 0
============================================================================
[...]
Ces tests sont également lancés lorsque cette commande est lancée :
make distcheck
check
.check
une fois l'extension compilée.L'intérêt des tests est de prévenir d'un changement non voulu dans le code de l'extension.
Modifiez le code de l'instruction sans changer les tests :
PLUGIN test
test: initialisation
%{
cat > input_file << EOM
12
4
EOM
%}
test: finalisation
%{
rm input_file
%}
test: "Basic"
%{
PLUGIN "svmint.so"
PROCESS "test"
CODE "main" INLINE
:memory INT/i, BLN/b
:test.instruction 12 3 -> &i
:int.cmp @&i = 4 -> &b
:shutdown 1 :unless @&b TRUE
:test.instruction 12 5 -> &i
:int.cmp @&i = 2 -> &b
:shutdown 1 :unless @&b TRUE
:test.instruction 12 30 -> &i
:int.cmp @&i = 0 -> &b
:shutdown 1 :unless @&b TRUE
:test.instruction 12 -3 -> &i
:int.cmp @&i = -4 -> &b
:shutdown 1 :unless @&b TRUE
:shutdown 0
END
END
%}
test: "Divisionbyzero"
%{
-- 42 "is the answer"
%}
%{
PROCESS "test"
CODE "main" INLINE
:interruption NUMERIC trap
:memory INT/i
:test.instruction 12 0 -> &i
:shutdown 1
:label trap
:shutdown 2 :when &i INITIALISED
:shutdown 0
END
END
%}
test: "File"
%{
PLUGIN "svmcom.so"
PLUGIN "svmstr.so"
PLUGIN "svmint.so"
PROCESS "test"
CODE "main" INLINE
:memory com.device/f, STR/s, INT/i, INT*2/r, BLN/b
:com.open com.file < "input_file" -> &f
0 -> &i
:label input
:com.read @&f com.line -> &s
:str.replace @&s 1 CONST str.pattern "\n" => ""
:int.parse @&s -> (r/@&i)
:shift &i
:goto input :when @&i IN r
:test.instruction @(r/0) @(r/1) -> &i
:int.cmp @&i = 3 -> &b
:shutdown 1 :unless @&b TRUE
END
END
%}
DEFINE
INSTRUCTION test.instruction INT INT -> INT
%{
auto l = ARGV_VALUE(0,integer);
auto r = ARGV_VALUE(1,integer);
if(r<=0)
{
ERROR_INTERNAL(NUMERIC,"Division by zero.");
}
return NEW_VALUE(integer,l/r);
%}
Générez et compilez l'extension, puis lancez les tests en compilation locale :
./plugin_install -l check
[...]
Test suite:
init: OK
Basic.svm: FAIL (return code: 125)
Divisionbyzero.svm: OK
File.svm: OK
fini: OK
Result: 2/3 passed.
Un test a bien échoué ici ! En effet, une des évaluations basiques lève maintenant une interruption (d'après le code de retour à 125, signifiant que le processus a été interrompu). Ici, comme plusieurs tests sont faits dans le même code, il faut déterminer où se situe l'erreur.
Ouvrez le fichier produit par la suite de test test/Basic.log :
Test case: Basic.svm
### Simple Virtual Machine 1234 : PROCESS test | 2023-01-01 00:00:00 GMT #############################################################################
Kernel interrupted: @(main, line 11) Interruption NUMERIC not handled: Division by zero.
Core dump:
Kernel:
State: I, transmit_interruption, interrupted @(main, line 11) Interruption NUMERIC not handled: Division by zero.
Processor:
State:
Next instruction: <main:1/11> (Current one: <main:1/10>)
Current memory : &0*0
Allocated memory: &0*2
Aliases : b i
Flags :
Cascaded flags :
Local interruption handlers:
Cascaded local interruption handlers:
Saved states:
Global interruption handlers:
Waiting interruptions:
NUMERIC
Code at <main:1/10>:
:memory INT/i , BLN/b # <0> @(main, line 1)
:test.instruction 12 3 -> &i # <1> @(main, line 2)
:int.cmp @&i = 4 -> &b # <2> @(main, line 3)
:shutdown 1 :unless @&b TRUE # <3> @(main, line 4) SYSTEM
:test.instruction 12 5 -> &i # <4> @(main, line 5)
:int.cmp @&i = 2 -> &b # <5> @(main, line 6)
:shutdown 1 :unless @&b TRUE # <6> @(main, line 7) SYSTEM
:test.instruction 12 30 -> &i # <7> @(main, line 8)
:int.cmp @&i = 0 -> &b # <8> @(main, line 9)
:shutdown 1 :unless @&b TRUE # <9> @(main, line 10) SYSTEM
====== HERE ======
:test.instruction 12 -3 -> &i # <10> @(main, line 11)
==================
:int.cmp @&i = -4 -> &b # <11> @(main, line 12)
:shutdown 1 :unless @&b TRUE # <12> @(main, line 13) SYSTEM
:shutdown 0 # <13> @(main, line 14) SYSTEM
Labels:
Symbols:
Memory:
&0: INT 0
&1: BLN TRUE
Aliases:
b -> &1*1
i -> &0*1
Free space:
### Simple Virtual Machine 1234 : SYSTEM | 2023-01-01 00:00:00 GMT ###################################################################################
Process test interrupted: @(main, line 11) Interruption NUMERIC not handled: Division by zero.
Result: FAIL (return code: 125)
L'instruction qui a levé une interruption inattendue est celle qui contient un argument négatif, ce qui est conforme au code de l'instruction, mais pas pris en compte dans le test :
Lorsqu'un test échoue avec la commande make check
d'Autotools, l'outil vous invite à ouvrir le fichier test/test-suite.log qui contient les mêmes informations.
Vous venez de voir comment ajouter des tests aux extensions.
Pour une extension qui doit rester stable dans le temps, il est toujours préférable d'y ajouter des tests pour assurer que le fonctionnement reste sous contrôle, même en cas d'évolution ultérieure de l'extension.
Les tests sont un outil simple et efficace pour écrire des extensions de qualité industrielle.