@@ -146,11 +146,8 @@ def _load(skip_lint: bool = False) -> Tuple[List[DefaultMunch], int]:
146146 return job_definitions , num_tests
147147
148148
149- def _copy_inputs (test_inputs : DefaultMunch ,
150- project_path : str ) -> bool :
149+ def _copy_inputs (test_inputs : List [str ], project_path : str ) -> bool :
151150 """Copies all the test files into the test project directory.
152- Files are expected to reside in the repo's 'data' directory
153- and must begin 'data/'
154151 """
155152
156153 # The files are assumed to reside in the repo's 'data' directory.
@@ -159,20 +156,19 @@ def _copy_inputs(test_inputs: DefaultMunch,
159156 expected_prefix : str = f'{ _DATA_DIRECTORY } /'
160157 for test_input in test_inputs :
161158
162- test_file : str = test_inputs [test_input ]
163- print (f'# + { test_file } ({ test_input } )' )
159+ print (f'# + { test_input } ' )
164160
165- if not test_file .startswith (expected_prefix ):
161+ if not test_input .startswith (expected_prefix ):
166162 print ('! FAILURE' )
167- print (f'! Input file { test_file } must start with "{ expected_prefix } "' )
163+ print (f'! Input file { test_input } must start with "{ expected_prefix } "' )
168164 return False
169- if not os .path .isfile (test_file ):
165+ if not os .path .isfile (test_input ):
170166 print ('! FAILURE' )
171- print (f'! Missing input file { test_file } ({ test_input } )' )
167+ print (f'! Missing input file { test_input } ({ test_input } )' )
172168 return False
173169
174170 # Looks OK, copy it
175- shutil .copy (test_file , project_path )
171+ shutil .copy (test_input , project_path )
176172
177173 print ('# Copied' )
178174
@@ -288,51 +284,110 @@ def _test(args: argparse.Namespace,
288284
289285 _print_test_banner (collection , job , job_test_name )
290286
287+ # The status changes to False if any
288+ # part of this block fails.
289+ test_status : bool = True
290+
291291 # Does the test have an 'ignore' declaration?
292292 if 'ignore' in job_definition .tests [job_test_name ]:
293293 print ('W Ignoring test (found "ignore")' )
294294 tests_ignored += 1
295295 continue
296296
297297 # Render the command for this test.
298+
298299 # First extract the variables and values from 'options'
299- # and then 'inputs'...
300+ # and then 'inputs'.
300301 job_variables : Dict [str , Any ] = {}
301302 for variable in job_definition .tests [job_test_name ].options :
302303 job_variables [variable ] = \
303304 job_definition .tests [job_test_name ].options [variable ]
305+
306+ # If the option variable's declaration is 'multiple'
307+ # it must be handled as a list, e.g. it might be declared like this: -
308+ #
309+ # The double-comment is used
310+ # to avoid mypy getting upset by the 'type' line...
311+ #
312+ # # properties:
313+ # # fragments:
314+ # # title: Fragment molecules
315+ # # multiple: true
316+ # # mime-types:
317+ # # - chemical/x-mdl-molfile
318+ # # type: file
319+ #
304320 # We only pass the basename of the input to the command decoding
305321 # i.e. strip the source directory.
322+
323+ # A list of input files (relative to this directory)
324+ # We populate this with everything we find declared as an input
325+ input_files : List [str ] = []
326+
327+ # Process every 'input'
306328 for variable in job_definition .tests [job_test_name ].inputs :
307- job_variables [variable ] = \
308- os .path .basename (job_definition .tests [job_test_name ]
309- .inputs [variable ])
310-
311- # Get the raw (encoded) command from the job definition...
312- raw_command : str = job_definition .command
313- # Decode it using our variables...
314- decoded_command , test_status = \
315- decoder .decode (raw_command ,
316- job_variables ,
317- 'command' ,
318- decoder .TextEncoding .JINJA2_3_0 )
319- if not test_status :
320- print ('! FAILURE' )
321- print ('! Failed to render command' )
322- print (f'! error={ decoded_command } ' )
323- # Record but do no further processing
324- tests_failed += 1
325- test_status = False
329+ # Test variable must be known as an input or option.
330+ # Is the variable an option (otherwise it's an input)
331+ variable_is_option : bool = False
332+ variable_is_input : bool = False
333+ if variable in job_definition .variables .options .properties :
334+ variable_is_option = True
335+ elif variable in job_definition .variables .inputs .properties :
336+ variable_is_input = True
337+ if not variable_is_option and not variable_is_input :
338+ print ('! FAILURE' )
339+ print (f'! Test variable ({ variable } )' +
340+ ' not declared as input or option' )
341+ # Record but do no further processing
342+ tests_failed += 1
343+ test_status = False
344+ # Is it declared as a list?
345+ value_is_list : bool = False
346+ if variable_is_option :
347+ if job_definition .variables .options .properties [variable ].multiple :
348+ value_is_list = True
349+ else :
350+ if job_definition .variables .inputs .properties [variable ].multiple :
351+ value_is_list = True
352+
353+ # Add each value or just one value
354+ # (depending on whether it's a list)
355+ if value_is_list :
356+ job_variables [variable ] = []
357+ for value in job_definition .tests [job_test_name ].inputs [variable ]:
358+ job_variables [variable ].append (os .path .basename (value ))
359+ input_files .append (value )
360+ else :
361+ value = job_definition .tests [job_test_name ].inputs [variable ]
362+ job_variables [variable ] = os .path .basename (value )
363+ input_files .append (value )
326364
327- # The command must not contain new-lines.
328- # So split then join the command.
329- job_command : str = '' .join (decoded_command .splitlines ())
365+ if test_status :
366+ # Get the raw (encoded) command from the job definition...
367+ raw_command : str = job_definition .command
368+ # Decode it using our variables...
369+ decoded_command , test_status = \
370+ decoder .decode (raw_command ,
371+ job_variables ,
372+ 'command' ,
373+ decoder .TextEncoding .JINJA2_3_0 )
374+ if not test_status :
375+ print ('! FAILURE' )
376+ print ('! Failed to render command' )
377+ print (f'! error={ decoded_command } ' )
378+ # Record but do no further processing
379+ tests_failed += 1
380+ test_status = False
330381
331382 # Create the test directories, docker-compose file
332383 # and copy inputs...
333384 t_compose : Optional [Compose ] = None
334385 if test_status :
335386
387+ # The command must not contain new-lines.
388+ # So split then join the command.
389+ job_command : str = '' .join (decoded_command .splitlines ())
390+
336391 print (f'> image={ job_image } ' )
337392 print (f'> command="{ job_command } "' )
338393
@@ -351,10 +406,8 @@ def _test(args: argparse.Namespace,
351406
352407 # Copy the data into the test's project directory.
353408 # Data's expected to be found in the Job's 'inputs'.
354- if job_definition .tests [job_test_name ].inputs :
355- test_status = \
356- _copy_inputs (job_definition .tests [job_test_name ].inputs ,
357- project_path )
409+ print (f'input_files={ input_files } ' )
410+ test_status = _copy_inputs (input_files , project_path )
358411
359412 # Run the container
360413 if test_status and not args .dry_run :
0 commit comments