PLUGIN unittest lang: "C++" version: "1.0" date: "2023-11-01" author: "Julien BRUGUIER" maintainer: "Julien BRUGUIER " synopsis: "A plugin to unittest SVM code." description: %{ This plugin allows unit testing of SVM code. .P To achieve this, this plugin opens code internals to other codes. .P It is advised to write your application in at least two files: - One file containing the application description, - The other file containing the application code. .P You can use the FILE option to the CODE directive in the application description file to load the code: .nf PROCESS "application" CODE "main" FILE "code.svm" ... END .fi .P Then, you can write a separated application description file to unit test your code: .nf PLUGIN "svmcom.so" PROCESS "unittest" CODE "main" INLINE :memory com.device/f, STR/s, LIB/code :com.open com.file < "code.svm" -> &f :com.read @&f com.all -> &s [ ] -> f :library "code" @&s -> &code # start unit tests here # Use :call $(@&code/"symbol") parameters to call a symbol # Use :unittest.call_local @&code "label" parameters to call a label # Use :unittest.run @&code "start" => "exit" to run a part of the code ... END ... END .fi %} example: "Local function call" %{ .nf #!/usr/bin/env svm LOG MACHINE PLUGIN "===PLUGINMACHINELIB===" PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :memory LIB/l, INT*3/p :library "testcode" """:com.message 1 :com.message 2 :label f :com.message @(P/0) :com.message @(P/1) :com.message @(P/2) :return :com.message 6 :com.message 7 """ -> &l [ 3 , 4 , 5 ] -> p :com.message "===" :unittest.call_local @&l "f" p :com.message "===" :unittest.call_local @&l 1 p :com.message "===" END END .fi %} example: "Stray testing" %{ .nf #!/usr/bin/env svm LOG MACHINE PLUGIN "===PLUGINMACHINELIB===" PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :memory LIB/l :library "testcode" """:com.message 1 :com.message 2 :label b :com.message 3 :com.message 4 :com.message 5 :label e :com.message 6 :com.message 7 """ -> &l :com.message "===" :unittest.run @&l "b" => "e" :com.message "===" :unittest.run @&l 1 => 6 :com.message "===" END END .fi %} test: initialisation %{ cat > function.svm << EOM :com.message 0 :com.message 1 :com.message 2 :label b :memory INT -> &P 1 -> &@&P :label l :com.message @(P/@&@&P) :shift &@&P :goto l :when @&@&P IN P :return :label e :com.message 7 :com.message 8 :com.message 9 :shutdown 1 EOM cat > stray.svm << EOM :com.message 0 :com.message 1 :com.message 2 :label b :com.message 3 :com.message 4 :com.message 5 :com.message 6 :label e :com.message 7 :com.message 8 :com.message 9 :shutdown 1 EOM %} test: finalisation %{ rm -f function.svm stray.svm %} test: "LocalCallExistantLabel" %{ PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :memory com.device/f, STR/s, LIB/c :com.open com.file < "function.svm" -> &f :com.read @&f com.all -> &s :library "code" @&s -> &c :com.message "===" :memory (PTR, INT*4)/p [ , 3 , 4 , 5 , 6 ] -> p :unittest.call_local @&c "b" p :com.message "===" END END %} test: "LocalCallInexistantLabel" %{ PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :interruption FAILURE pass :memory com.device/f, STR/s, LIB/c :com.open com.file < "function.svm" -> &f :com.read @&f com.all -> &s :library "code" @&s -> &c :com.message "===" :memory (PTR, INT*4)/p [ , 3 , 4 , 5 , 6 ] -> p :unittest.call_local @&c "i" p :com.message "===" :shutdown 1 :label pass END END %} test: "LocalCallAddress" %{ PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :memory com.device/f, STR/s, LIB/c :com.open com.file < "function.svm" -> &f :com.read @&f com.all -> &s :library "code" @&s -> &c :com.message "===" :memory (PTR, INT*4)/p [ , 3 , 4 , 5 , 6 ] -> p :unittest.call_local @&c 3 p :com.message "===" END END %} test: "RunExistantLabels" %{ PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :memory com.device/f, STR/s, LIB/c :com.open com.file < "stray.svm" -> &f :com.read @&f com.all -> &s :library "code" @&s -> &c :com.message "===" :unittest.run @&c "b" => "e" :com.message "===" END END %} test: "RunInexistantLabelsStart" %{ PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :interruption FAILURE pass :memory com.device/f, STR/s, LIB/c :com.open com.file < "stray.svm" -> &f :com.read @&f com.all -> &s :library "code" @&s -> &c :com.message "===" :unittest.run @&c "i" => "e" :com.message "===" :shutdown 1 :label pass END END %} test: "RunInexistantLabelsExit" %{ PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :interruption FAILURE pass :memory com.device/f, STR/s, LIB/c :com.open com.file < "stray.svm" -> &f :com.read @&f com.all -> &s :library "code" @&s -> &c :com.message "===" :unittest.run @&c "b" => "i" :com.message "===" :shutdown 1 :label pass END END %} test: "RunValidAddress" %{ PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :memory com.device/f, STR/s, LIB/c :com.open com.file < "stray.svm" -> &f :com.read @&f com.all -> &s :library "code" @&s -> &c :com.message "===" :unittest.run @&c 3 => 7 :com.message "===" END END %} test: "RunInvalidAddressStart" %{ PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :interruption FAILURE pass :memory com.device/f, STR/s, LIB/c :com.open com.file < "stray.svm" -> &f :com.read @&f com.all -> &s :library "code" @&s -> &c :com.message "===" :unittest.run @&c -3 => 7 :com.message "===" :shutdown 1 :label pass END END %} test: "RunInvalidAddressExit" %{ PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :interruption FAILURE pass :memory com.device/f, STR/s, LIB/c :com.open com.file < "stray.svm" -> &f :com.read @&f com.all -> &s :library "code" @&s -> &c :com.message "===" :unittest.run @&c 3 => -7 :com.message "===" :shutdown 1 :label pass END END %} test: "RunInvalidInstruction" %{ PLUGIN "svmcom.so" PROCESS "test" CODE "main" INLINE :memory PEP/i unittest.exit -> &i :interruption PROCESSOR pass :@&i $"s" :shutdown 1 :symbol pass END END %} code: %{ namespace UnitTest { SVM_Value_Symbol get_symbol(const void *svm, SVM_Code c, SVM_Value l, const std::string& where) { SVM_Address b=0; if(::svm_value_type_is_integer(svm,l)) { auto i = ::svm_value_integer_get(svm,l); if(i<0) { std::stringstream oss; oss << "Negative address not allowed for " << where << "."; ERROR_INTERNAL(FAILURE,oss.str().c_str()); } b = static_cast(i); } else { SVM_Boolean tl = ::svm_code_label_has_address(svm,c,l); if(not tl) { std::stringstream oss; oss << "Invalid label for " << where << "."; ERROR_INTERNAL(FAILURE,oss.str().c_str()); } b = ::svm_code_label_get_address(svm,c,l); } return ::svm_value_symbol_new(svm,c,b); } } %} DEFINE SYSTEM INSTRUCTION unittest.call_local LIB:code [ STR INT ]:function PTR:parameters %{ SVM_Value_Library l = ::svm_parameter_value_get(svm,argv[0]); SVM_Code c = ::svm_value_library_get_code(svm,l); SVM_Value_Symbol f = ::UnitTest::get_symbol(svm,c,::svm_parameter_value_get(svm,argv[1]),"local function"); SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[2]); ::svm_processor_call_global(svm,CURRENT(kernel),f,p); %} help: %{ This instruction performs a call to a local function of a code. .P This allows internal functions testing without having to switch labels into symbols. %} SYSTEM INSTRUCTION unittest.run LIB:code [ STR INT ]:start => [ STR INT ]:exit %{ SVM_Value_Library l = ::svm_parameter_value_get(svm,argv[0]); SVM_Code c = ::svm_value_library_get_code(svm,l); SVM_Value_Symbol b = ::UnitTest::get_symbol(svm,c,::svm_parameter_value_get(svm,argv[1]),"unittest start"); SVM_Value_Symbol e = ::UnitTest::get_symbol(svm,c,::svm_parameter_value_get(svm,argv[3]),"unittest exit"); SVM_Value_Symbol r = ::svm_processor_get_nextinstruction(svm,CURRENT(kernel)); SVM_Parameter *p = ::svm_parameter_array_new(svm,1); p[0] = ::svm_parameter_value_new(svm,r); ::svm_processor_instructionoverride_set_global(svm,CURRENT(kernel),e,CONST_PEP(unittest,exit),1,p,GLOBAL); ::svm_debug_breakpoint_add_break(svm,CURRENT(kernel),b); ::svm_processor_jump_global(svm,CURRENT(kernel),b); %} help: %{ This instruction performs a jump to an arbitrary location in the code. It also sets an exit point (using the :unittest.exit instruction) on an arbitrary location in the code. .P The execution will then pass to the beginning of the tested section, until the exit (excluded). Once the exit is reached, the execution continues after this instruction (The exit acts as a return). %} OVERRIDE INSTRUCTION unittest.exit SYM:return %{ SVM_Value_Symbol s = ::svm_parameter_value_get(svm,argv[0]); ::svm_processor_jump_global(svm,CURRENT(kernel),s); ::svm_processor_instructionoverride_reset_global(svm,CURRENT(kernel),::svm_processor_get_currentinstruction(svm,CURRENT(kernel)),GLOBAL); %} help: %{ This instruction is used by the :unittest.run instruction, and should not be used alone. %}