diff --git a/deploy/packaging/linux.xml b/deploy/packaging/linux.xml index bf87a2f0c5..182302d729 100644 --- a/deploy/packaging/linux.xml +++ b/deploy/packaging/linux.xml @@ -788,6 +788,7 @@ rm -f /etc/ld.so.conf.d/mdsplus.conf 2>/dev/null + diff --git a/matlab/testing/run_tests.py b/matlab/testing/run_tests.py new file mode 100755 index 0000000000..d2aa5a1ec1 --- /dev/null +++ b/matlab/testing/run_tests.py @@ -0,0 +1,491 @@ +#!/usr/bin/env python3 + +# TODO: This only tests the "thin-client" API for MATLAB. Eventually, should +# also incorporate the "thick-client" API tests in the "mdstest.m" file. +# NOTE: The following test harness and tests are derived from the IDL tests. + +import os +import subprocess +import argparse +import string + +# The default values are intended to be used from within the PSFC network +# If you want to run these tests on your own infrastructure, provide the +# details as arguments to customize the tests to your servers/trees/nodes + +parser = argparse.ArgumentParser() + +parser.add_argument( + '--mdsip-server', + default='alcdaq6', + help='The server to call mdsconnect() on' +) + +parser.add_argument( + '--database-name', + default='logbook', + help='The database to call set_database() on' +) + +parser.add_argument( + '--tree', + default='cmod', + help='The tree to mdsopen()' +) + +parser.add_argument( + '--shot', + default=1090909009, + help='The shot to mdsopen()' +) + +parser.add_argument( + '--node1', + default='SUM(\\IP)', + help='An expression to evaluate and compare against --node1-value' +) + +parser.add_argument( + '--node1-value', + default='-69662768', + help='The value of evaluating the --node1 expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--node2', + default='TSTART', + help='An expression to evaluate and compare against --node2-value' +) + +parser.add_argument( + '--node2-value', + default='-4', + help='The value of evaluating the --node2 expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--text', + default='ADMIN.LOGBOOK.STATISTICS:ANAL_DONE', + help='An expression to evaluate and compare against --text-value' +) + +parser.add_argument( + '--text-value', + default='9-SEP-2009 11:29:41.00', + help='The value of evaluating the --text expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--numeric', + default='SEQUENCE_NUM', + help='An expression to evaluate and compare against --numeric-value' +) + +parser.add_argument( + '--numeric-value', + default='35575', + help='The value of evaluating the --numeric expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--signal', + default='ADMIN.FAST_WINDOW.XTOMO:OPTIONS', + help='An expression to evaluate and compare against --signal-value' +) + +# default="{'none '} {'25kHz '} {'50kHz '} {'83.3kHz'}" +parser.add_argument( + '--signal-value', + default='"none " "25kHz " "50kHz " "83.3kHz"', + help='The value of evaluating the --signal expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--fullpath', + default='\\CMOD::TOP.ELECTRONS.ECE.RESULTS:APERTURE', + help='An expression to evaluate and compare against --fullpath-value' +) + +parser.add_argument( + '--fullpath-value', + default='39.1000', + help='The value of evaluating the --fullpath expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--relative-def', + default='\CMOD::TOP.ELECTRONS.ECE', + help='An expression to change the default position in the tree' +) + +parser.add_argument( + '--relative', + default='.RESULTS:ZPD', + help='An expression to evaluate and compare against --relative-value' +) + +parser.add_argument( + '--relative-value', + default='57', + help='The value of evaluating the --relative expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--reset-def', + default='\CMOD::TOP', + help='An expression to reset the default position to the top of the tree' +) + +parser.add_argument( + '--tag', + default='\\DNB::TSTART', + help='An expression to evaluate and compare against --tag-value' +) + +parser.add_argument( + '--tag-value', + default='-4', + help='The value of evaluating the --tag expression, ignoring leading/trailing whitespace' +) + +# The parser is converting "\\" into "\", so to get desired path of "\\CMOD::" must use "\\\\" +parser.add_argument( + '--wildcard', + default='SUBSCRIPT(GETNCI("\\\\CMOD::TOP.***:CPLD_START", "FULLPATH"),0)', + help='An expression to evaluate and compare against --wildcard-value' +) + +parser.add_argument( + '--wildcard-value', + default='\CMOD::TOP.DNB.MIT_CXRS:CPLD_START', + help='The value of evaluating the --wildcard expression, ignoring leading/trailing whitespace' +) + +# The parser is converting "\\" into "\", so to get desired path of "\\CMOD::" must use "\\\\" +parser.add_argument( + '--getnci', + default=' SUBSCRIPT(GETNCI("\\\\CMOD::TOP.DNB.MIT_CXRS:CPLD_START", "USAGE"),0) ', + help='An expression to evaluate and compare against --getnci-value' +) + +parser.add_argument( + '--getnci-value', + default='5', + help='The value of evaluating the --getnci expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--rank', + default='RANK(\\IP)', + help='An expression to evaluate and compare against --rank-value' +) + +parser.add_argument( + '--rank-value', + default='1', + help='The value of evaluating the --rank expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--ndims', + default='NDIMS(\\IP)', + help='An expression to evaluate and compare against --ndims-value' +) + +parser.add_argument( + '--ndims-value', + default='1', + help='The value of evaluating the --ndims expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--dim-of', + default='DIM_OF(\\CMOD::TOP.ADMIN.FAST_WINDOW.XTOMO:OPTIONS, 0)', + help='An expression to evaluate and compare against --dim-of-value' +) + +parser.add_argument( + '--dim-of-value', + default='1 1 1 1', + help='The value of evaluating the --dim-of expression, ignoring leading/trailing whitespace' +) + +parser.add_argument( + '--units-of', + default='UNITS_OF(\\CMOD::TOP.MHD.XTOMO.SIGNALS.ARRAY_1:CHORD_01)', + help='An expression to evaluate and compare against --units-of-value' +) + +parser.add_argument( + '--units-of-value', + default='watts', + help='The value of evaluating the --units-of expression, ignoring leading/trailing whitespace' +) + +args = parser.parse_args() + + +#--------------------------------------------------------------------------- +# Each write test should start with a clean tree. +# Write tests use a local tree, but eventually will be upgraded to use mdsip. +# TODO: The default_tree_path is temporary; will change when switch to mdsip. +# +def build_write_tree(tree, shot): + import os + import MDSplus as mds + + test_dir = os.getenv('MDSPLUS_DIR') + '/matlab/testing/' + os.environ['default_tree_path'] = test_dir # required to write tree + + tree_name = tree + '_' + str(shot) + tree_file = test_dir + tree_name + + tc = tree_file + '.characteristics' + td = tree_file + '.datafile' + tt = tree_file + '.tree' + + if os.path.exists(tc): + os.remove(tc) + if os.path.exists(td): + os.remove(td) + if os.path.exists(tt): + os.remove(tt) + + t = mds.Tree(tree, shot, 'new') + + nid = t.addNode('A_TEXT', 'text') + nid = t.addNode('B_NUM', 'numeric') + nid = t.addNode('C_SIGNAL', 'signal') + + nid = t.addNode('SUBTREE_1', 'structure') + nid = t.addNode('SUBTREE_1.D_TEXT', 'text') + nid = t.addNode('SUBTREE_1.E_UNITS', 'numeric') + + nid = t.addNode('SUBTREE_2', 'structure') + nid = t.addNode('SUBTREE_2.F_NUM', 'numeric') + nid = t.addNode('SUBTREE_2.G_NUM', 'numeric') + nid.addTag('TAG_G') + + t.write() + t.close() + +write_tree = 'write' +write_shot = 123 +build_write_tree(write_tree, write_shot) + + +all_tests_passed = True +def matlab_test(code, expected_output): + global all_tests_passed + + header_end = 'For product information, visit www.mathworks.com.' + + code_lines = [ line.strip() for line in code.splitlines() ] + code_lines = list(filter(None, code_lines)) + code = '\n'.join(code_lines) + + # Write the test to a MATLAB script file, test.m + + code = '% placeholder comment\n' + code + '\nexit\n' + open('test.m', 'wt').write(code) + + expected_lines = [ line.strip() for line in expected_output.splitlines() ] + expected_lines = list(filter(None, expected_lines)) + + print('Running:') + for line in code_lines: + print(f'MATLAB> {line}') + print() + + print('Expected:') + for line in expected_lines: + print(line) + print() + + proc = subprocess.Popen( + ['matlab', '-nodisplay'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + + # We call our test.m module + + proc.stdin.write('test\n'.encode()) + proc.stdin.flush() + + hide_output = True + strip_set = string.whitespace + ">" # Excludes MATLAB's >> prompt + + print('Actual:') + lines = [] + while True: + line = proc.stdout.readline().decode() + if not line: + break + + line = line.strip(strip_set) + + if line is not None and not hide_output: + if line != '': + print(line) + lines.append(line.strip()) + + # Skip the MATLAB header and licensing information, which is everything + # above this line + if line == header_end: + hide_output = False + print() + + this_test_passed = True + + for line, expected in zip(lines, expected_lines): + if line != expected: + print(f'`{line}` != `{expected}`') + this_test_passed = False + all_tests_passed = False + + if this_test_passed: + print('Success') + else: + print('Failure') + + print() + print('----------------------------------------------------------------\n') + print() + + +#------------------------------------------------------------------------------- +# Tree open / read / close +matlab_test(f''' + +testid = 'MATLAB-tree-read'; +mdsconnect('{args.mdsip_server}'); +mdsopen('{args.tree}', {args.shot}); +disp(mdsvalue('{args.node1}')); +disp(mdsvalue('{args.node2}')); +mdsclose(); +mdsdisconnect(); + +''', +f''' + +{args.node1_value} +{args.node2_value} + +''') + + +#------------------------------------------------------------------------------- +# https://github.com/MDSplus/mdsplus/issues/2639 +# Issue #2639: mdsvalue works without a socket +matlab_test(f''' + +testid = 'MATLAB-2639-no-socket'; +DATA = '55'; +disp(mdsvalue(DATA)); + +''', +''' + +55 + +''') + + +#--------------------------------------------------------------------------- +# Read: various permutations of reading data from a tree +matlab_test(f''' + +testid = 'MATLAB-read-various'; + +mdsconnect('{args.mdsip_server}'); +mdsopen('{args.tree}', {args.shot}); +disp(mdsvalue('{args.text}')); +disp(mdsvalue('{args.numeric}')); +disp(transpose(mdsvalue('{args.signal}'))); +disp(mdsvalue('{args.fullpath}')); +mdstcl('set def {args.relative_def}'); +disp(mdsvalue('{args.relative}')); +mdstcl('set def {args.reset_def}'); +disp(mdsvalue('{args.tag}')); +disp(mdsvalue('{args.wildcard}')); +disp(mdsvalue('{args.getnci}')); +disp(mdsvalue('{args.rank}')); +disp(mdsvalue('{args.ndims}')); +disp(transpose(mdsvalue('{args.dim_of}'))); +disp(mdsvalue('{args.units_of}')); +mdsclose(); +mdsdisconnect(); + +''', +f''' + +{args.text_value} +{args.numeric_value} +{args.signal_value} +{args.fullpath_value} +{args.relative_value} +{args.tag_value} +{args.wildcard_value} +{args.getnci_value} +{args.rank_value} +{args.ndims_value} +{args.dim_of_value} +{args.units_of_value} + +''') + + +#--------------------------------------------------------------------------- +# Write: various permutations of writing data to a tree +# +# Note: Uses local tree on the build server (i.e., not using mdsip). +# The $default_tree_path must point to the "matlab/testing" directory. +matlab_test(f''' + +testid = 'MATLAB-write-various'; + +mdsopen('{write_tree}', {write_shot}); +mdsput('A_TEXT', ' "string_a" '); +mdsput('B_NUM', '22'); +mdsput('C_SIGNAL', 'build_signal([10,-10,5,-5,0],[10.2,-10.2,5.4,-5.4,0.0], [0 .. 4])'); +mdsput('\TOP.SUBTREE_1:D_TEXT', ' "string_d" '); +mdsput('SUBTREE_1.E_UNITS', 'build_with_units(55, "volts")'); +mdstcl('set def SUBTREE_1'); +mdsput('.-.SUBTREE_2:F_NUM', '66'); +mdsput('\TAG_G', '77'); +mdsclose(); + +mdsopen('{write_tree}', {write_shot}); +disp(mdsvalue('A_TEXT')); +disp(mdsvalue('B_NUM')); +disp(transpose(mdsvalue('DATA(C_SIGNAL)'))); +disp(transpose(mdsvalue('RAW_OF(C_SIGNAL)'))); +disp(transpose(mdsvalue('DIM_OF(C_SIGNAL)'))); +disp(mdsvalue('SUBTREE_1:D_TEXT')); +disp(mdsvalue('SUBTREE_1.E_UNITS')); +disp(mdsvalue('UNITS_OF(SUBTREE_1.E_UNITS)')); +disp(mdsvalue('SUBTREE_2:F_NUM')); +disp(mdsvalue('SUBTREE_2:G_NUM')); +mdsclose(); + +''', +''' + +string_a +22 +10 -10 5 -5 0 +10.2000 -10.2000 5.4000 -5.4000 0 +0 1 2 3 4 +string_d +55 +volts +66 +77 + +''') + +if not all_tests_passed: + exit(1) +