def has_equal_references(state, absolute=False, incorrect_msg=None): child = check_range(state, field="formulas", field_msg="formula") if absolute: pattern = r"\$?[A-Z]+\$?\d+(?:\:\$?[A-Z]+\$?\d+)?" else: pattern = r"[A-Za-z]+\d+(?:\:[A-Za-z]+\d+)?" student_formulas = child.student_data solution_formulas = child.solution_data for i, student_row in enumerate(student_formulas): for j, student_cell in enumerate(student_row): solution_cell = str(solution_formulas[i][j]) student_cell = str(student_cell) solution_references = re.findall(pattern, solution_cell) for reference in solution_references: if normalize_formula(reference) not in normalize_formula( student_cell): _msg = ( incorrect_msg or ("In cell `{range}`, did you use the{absolute_str} " "reference `{reference}`?")).format( absolute_str=" absolute" if absolute else "", reference=reference, **child.to_message_exposed_dict()) child.report(_msg)
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
def check_function(state, name, index=0, missing_msg=None): missing_msg = ( missing_msg or "In cell `{range}`, did you use the `{name}()` function?").format( name=name, **state.to_message_exposed_dict()) # construct regex pattern pattern = "(?:{pattern}.*){{{index}}}".format( pattern=normalize_formula(name), index=index + 1) has_code( state, pattern=pattern, fixed=False, incorrect_msg=missing_msg, normalize=normalize_formula, ) # Don't return state; chaining not implemented yet return None
def has_equal_pivot(state, extra_msg=None): child = check_range(state, field="pivotTables", field_msg="pivot table") student_pivot_tables = child.student_data["pivotTables"] solution_pivot_tables = child.solution_data["pivotTables"] for i, student_row in enumerate(student_pivot_tables): for j, student_pivot_table in enumerate(student_row): solution_pivot_table = solution_pivot_tables[i][j] issues = [] bound_rules = { key: RuleClass(student_pivot_table, solution_pivot_table, issues) for key, RuleClass in rule_types.items() } bound_rules["existence"]("rows", "There are no rows."), bound_rules["existence"]("columns", "There are no columns.") bound_rules["existence"]("values", "There are no values.") bound_rules["existence"]("criteria", "There are no filters.") bound_rules["over_existence"]( "rows", "There are rows but there shouldn't be."), bound_rules["over_existence"]( "columns", "There are columns but there shouldn't be.") bound_rules["over_existence"]( "values", "There are values but there shouldn't be.") bound_rules["equality"]("source", "The source data is incorrect.") bound_rules["array_equality"]( ("rows", ["sortOrder"]), ("Inside rows, expected the {ordinal} sort order to be " "`{expected}`, but got `{actual}`"), ) bound_rules["array_equality"]( ("columns", ["sortOrder"]), ("Inside columns, expected the {ordinal} sort order to be " "`{expected}`, but got `{actual}`"), ) bound_rules["array_equality"]( ("rows", ["valueBucket"]), ("Inside rows, expected the {ordinal} sort group to be " "`{expected}`, but got `{actual}`"), ) bound_rules["array_equality"]( ("columns", ["valueBucket"]), ("Inside columns, expected the {ordinal} sort group to be " "`{expected}`, but got `{actual}`"), ) bound_rules["array_equality"]( ("rows", ["sourceColumnOffset"]), ("Inside rows, the {ordinal} grouping variable is incorrect."), ) bound_rules["array_equality"]( ("columns", ["sourceColumnOffset"]), ("Inside columns, the {ordinal} grouping variable is incorrect." ), ) bound_rules["array_equality"]( ("rows", ["showTotals"]), ("Inside rows, the {ordinal} totals are not showing."), ) bound_rules["array_equality"]( ("columns", ["showTotals"]), ("Inside columns, the {ordinal} totals are not showing."), ) bound_rules["array_equality"]( ("values", ["summarizeFunction"]), ("Inside values, expected the {ordinal} summarize function " "to be `{expected}`, but got `{actual}`."), ) bound_rules["array_equality"]( ("values", ["calculatedDisplayType"]), ("Inside values, expected the {ordinal} display type " "to be `{expected}`, but got `{actual}`."), ) bound_rules["array_equal_length"]( "values", ("The number of values is incorrect. " "Expected {expected}, but got {actual}."), ) bound_rules["array_equal_length"]( "rows", ("The number of rows is incorrect. " "Expected {expected}, but got {actual}."), ) bound_rules["array_equal_length"]( "columns", ("The number of columns is incorrect. " "Expected {expected}, but got {actual}."), ) bound_rules["dict_key_equality"]( "criteria", "The rows or columns used in the filter are incorrect.") for key in dict_keys( safe_glom(solution_pivot_table, "criteria"), safe_glom(student_pivot_table, "criteria"), ): bound_rules["set_equality"]( f"criteria.{key}.visibleValues", "The filtered out values are incorrect.", ) bound_rules["array_equality"]( ("values", ["formula"]), "The {ordinal} value does not contain the correct calculated field.", lambda x, y: normalize_formula(x) == normalize_formula(y), ) 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 pivot table " f"at `{child.sct_range}`:\n\n{_issues_msg}\n") child.do_test(_msg)
def has_equal_pivot(state, extra_msg=None): child = check_range(state, field="pivotTables", field_msg="pivot table") student_pivot_tables = child.student_data solution_pivot_tables = child.solution_data for i, student_row in enumerate(student_pivot_tables): for j, student_pivot_table in enumerate(student_row): solution_pivot_table = solution_pivot_tables[i][j] test_runner = TestRunnerProxy(state.reporter) selector = dispatcher_selector(student_pivot_table, solution_pivot_table) tests = [ ExistenceTest(*selector("rows"), "There are no rows."), ExistenceTest(*selector("columns"), "There are no columns."), ExistenceTest(*selector("values"), "There are no values."), ExistenceTest(*selector("criteria"), "There are no filters."), OverExistenceTest(*selector("rows"), "There are rows but there shouldn't be."), OverExistenceTest(*selector("columns"), "There are columns but there shouldn't be."), OverExistenceTest(*selector("values"), "There are values but there shouldn't be."), EqualityTest(*selector("source"), "The source data is incorrect."), ] tests += array_element_tests( EqualityTest, *selector(("rows", ["sortOrder"])), ("Inside rows, expected the {ordinal} sort order to be " "`{expected}`, but got `{actual}`"), ) tests += array_element_tests( EqualityTest, *selector(("columns", ["sortOrder"])), ("Inside columns, expected the {ordinal} sort order to be " "`{expected}`, but got `{actual}`"), ) tests += array_element_tests( EqualityTest, *selector(("rows", ["valueBucket"])), ("Inside rows, expected the {ordinal} sort group to be " "`{expected}`, but got `{actual}`"), ) tests += array_element_tests( EqualityTest, *selector(("columns", ["valueBucket"])), ("Inside columns, expected the {ordinal} sort group to be " "`{expected}`, but got `{actual}`"), ) tests += array_element_tests( EqualityTest, *selector(("rows", ["sourceColumnOffset"])), ("Inside rows, the {ordinal} grouping variable is incorrect."), ) tests += array_element_tests( EqualityTest, *selector(("columns", ["sourceColumnOffset"])), ("Inside columns, the {ordinal} grouping variable is incorrect." ), ) tests += array_element_tests( EqualityTest, *selector(("rows", ["showTotals"])), ("Inside rows, the {ordinal} totals are not showing."), ) tests += array_element_tests( EqualityTest, *selector(("columns", ["showTotals"])), ("Inside columns, the {ordinal} totals are not showing."), ) tests += array_element_tests( EqualityTest, *selector(("values", ["summarizeFunction"])), ("Inside values, expected the {ordinal} summarize function " "to be `{expected}`, but got `{actual}`."), ) tests += array_element_tests( EqualityTest, *selector(("values", ["calculatedDisplayType"])), ("Inside values, expected the {ordinal} display type " "to be `{expected}`, but got `{actual}`."), ) tests += [ ArrayEqualLengthTest( *selector("values"), ("The number of values is incorrect. " "Expected {expected}, but got {actual}."), ), ArrayEqualLengthTest( *selector("rows"), ("The number of rows is incorrect. " "Expected {expected}, but got {actual}."), ), ArrayEqualLengthTest( *selector("columns"), ("The number of columns is incorrect. " "Expected {expected}, but got {actual}."), ), ] tests.append( DictKeyEqualityTest( *selector("criteria"), "The rows or columns used in the filter are incorrect.", )) for key in dict_keys(*selector("criteria")): tests.append( SetEqualityTest( *selector(f"criteria.{key}.visibleValues"), "The filtered out values are incorrect.", )) tests += array_element_tests( EqualityTest, *selector(("values", ["formula"])), "The {ordinal} value does not contain the correct calculated field.", lambda x, y: normalize_formula(x) == normalize_formula(y), ) 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 pivot table " f"at `{child.sct_range}`:\n\n{_issues_msg}\n") child.report(_msg)