Introduction

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.

Mise en place

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);
%}

Ecriture des tests

Tests simples

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 :

Tests avec environnement

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 :

Lancement des tests

Extension locale

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é.

Avec Autotools

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

Investiguer un test en échec

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.

Conclusion

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.