def array_element_tests(test, student_data, solution_data, feedback, *args, **kwargs): tests = [] if not isinstance(student_data, list) or not isinstance(solution_data, list): # If student data is None; Feedback will be provided by a different test. return tests for i, (element_student_data, element_solution_data) in enumerate( zip(student_data, solution_data) ): if isinstance(feedback, str): item_feedback = Feedback(feedback) else: item_feedback = copy.deepcopy(feedback) item_feedback.message = item_feedback.message.format( ordinal=selectors.get_ord(i + 1), expected=element_solution_data, actual=element_student_data, ) tests.append( test( element_student_data, element_solution_data, item_feedback, *args, **kwargs, ) ) return tests
def has_equal_conditional_formats(state, absolute=False, incorrect_msg=None): student_cond_formats = state.student_data["conditionalFormats"] solution_cond_formats = state.solution_data["conditionalFormats"] issues = [] for i, (student_cond_format, solution_cond_format) in enumerate( zip(student_cond_formats, solution_cond_formats) ): ordinal = selectors.get_ord(i + 1) bound_rules = { key: RuleClass(student_cond_format, solution_cond_format, issues) for key, RuleClass in rule_types.items() } bound_rules["existence"]( "booleanRule", f"The {ordinal} rule is incorrect, expected single color." ) bound_rules["existence"]( "gradientRule", f"The {ordinal} rule is incorrect, expected color scale." ) if len(issues) == 0: bound_rules["equality"]( "ranges", f"There ranges of the {ordinal} rule are incorrect." ) bound_rules["equality"]( "booleanRule.condition", f"There condition of the {ordinal} rule is incorrect.", ) bound_rules["equality"]( "booleanRule.format", f"There format of the {ordinal} rule is incorrect.", ) bound_rules["equality"]( "gradientRule.minpoint", f"There minpoint of the {ordinal} rule is incorrect.", ) bound_rules["equality"]( "gradientRule.midpoint", f"There minpoint of the {ordinal} rule is incorrect.", ) bound_rules["equality"]( "gradientRule.maxpoint", f"There maxpoint of the {ordinal} rule is incorrect.", ) nb_issues = len(issues) if nb_issues > 0: _issues_msg = "\n".join([f"- {issue}" for issue in issues]) _msg = ( f"There {'are' if nb_issues > 1 else 'is'} {nb_issues} " f"issue{'s' if nb_issues > 1 else ''} with the conditional " f"formatting rules:\n\n{_issues_msg}\n" ) state.do_test(_msg)
def has_equal_single_series(state, number, min_range_str, series_type): base_path = f"basicChart.series.{number - 1}" min_range = Range(min_range_str) series_path = { "basicChart": { "source": f"{base_path}.series.sourceRange.sources", "color": f"{base_path}.color", } } series_equality = {"source": partial(equal_sources, min_range=min_range)} ordinal = selectors.get_ord(number) if state.node_name in series_path: state.do_test( EqualityTest( *state_selector(state)(series_path[state.node_name].get(series_type)), f"the {ordinal} series' {series_type} is not correct.", series_equality.get(series_type, lambda x, y: x == y), ) ) return state
def __call__(self, path, message, equal_func=lambda x, y: x == y): solution_array = safe_glom(self.solution_structure, path) student_array = safe_glom(self.student_structure, path) if not isinstance(student_array, list): return if not isinstance(solution_array, list): return if solution_array != student_array: matches = [equal_func(x, y) for x, y in zip(student_array, solution_array)] mismatch_reducer = lambda all, x: all if x[1] else [*all, x[0]] mismatch_indices = functools.reduce( mismatch_reducer, enumerate(matches), [] ) self.issues.extend( [ message.format( ordinal=selectors.get_ord(i + 1), expected=solution_array[i], actual=student_array[i], ) for i in mismatch_indices ] )
def test_ord(num, ord): assert get_ord(num) == ord
def has_equal_conditional_formats(state, absolute=False, incorrect_msg=None): sct_range_filter = partial(conditional_format_filter, state.sct_range) student_cond_formats = list( filter(sct_range_filter, state.student_data["conditionalFormats"])) solution_cond_formats = list( filter(sct_range_filter, state.solution_data["conditionalFormats"])) test_runner = TestRunnerProxy(state.reporter) if len(student_cond_formats) < len(solution_cond_formats): state.report( f"There aren't enough conditional format rules defined at `{state.sct_range}`." ) for i, (student_cond_format, solution_cond_format) in enumerate( zip(student_cond_formats, solution_cond_formats)): ordinal = selectors.get_ord(i + 1) selector = dispatcher_selector(student_cond_format, solution_cond_format) test_runner.do_test( ExistenceTest( *selector("booleanRule"), f"The {ordinal} rule is incorrect, expected single color.", )) test_runner.do_test( ExistenceTest( *selector("gradientRule"), f"The {ordinal} rule is incorrect, expected color scale.", )) if not test_runner.has_failed: def normalize_condition(condition): if condition and condition.get("type", None) == "CUSTOM_FORMULA": condition = copy.deepcopy(condition) condition["values"][0][ "userEnteredValue"] = normalize_formula( condition["values"][0]["userEnteredValue"]) return condition tests = [ EqualityTest( *map(normalize_condition, selector("booleanRule.condition")), f"The condition of the {ordinal} rule is incorrect.", ), EqualityTest( *selector("booleanRule.format"), f"The format of the {ordinal} rule is incorrect.", ), EqualityTest( *selector("gradientRule.minpoint"), f"The minpoint of the {ordinal} rule is incorrect.", ), EqualityTest( *selector("gradientRule.midpoint"), f"The minpoint of the {ordinal} rule is incorrect.", ), EqualityTest( *selector("gradientRule.maxpoint"), f"The maxpoint of the {ordinal} rule is incorrect.", ), ] test_runner.do_tests(tests) nb_issues = len(test_runner.failures) if nb_issues > 0: _issues_msg = "\n".join( [f"- {test.feedback.message}" for test in test_runner.failures]) _msg = (f"There {'are' if nb_issues > 1 else 'is'} {nb_issues} " f"issue{'s' if nb_issues > 1 else ''} with the conditional " f"formatting rules:\n\n{_issues_msg}\n") state.report(_msg)