def assert_rule_pass_in_sql(code, sql, configs=None, msg=None): """Assert that a given rule doesn't fail on the given sql.""" # Configs allows overrides if we want to use them. if configs is None: configs = {} core = configs.setdefault("core", {}) core["rules"] = code cfg = FluffConfig(configs=configs) linter = Linter(config=cfg) # This section is mainly for aid in debugging. rendered = linter.render_string(sql, fname="<STR>", config=cfg, encoding="utf-8") parsed = linter.parse_rendered(rendered, recurse=True) if parsed.violations: if msg: print(msg) # pragma: no cover pytest.fail(parsed.violations[0].desc() + "\n" + parsed.tree.stringify()) print(f"Parsed:\n {parsed.tree.stringify()}") # Note that lint_string() runs the templater and parser again, in order to # test the whole linting pipeline in the same way that users do. In other # words, the "rendered" and "parsed" variables above are irrelevant to this # line of code. lint_result = linter.lint_string(sql, config=cfg, fname="<STR>") lerrs = lint_result.violations print(f"Errors Found: {lerrs}") if any(v.rule.code == code for v in lerrs): if msg: print(msg) # pragma: no cover pytest.fail(f"Found {code} failures in query which should pass.", pytrace=False)
def assert_rule_fail_in_sql(code, sql, configs=None, line_numbers=None): """Assert that a given rule does fail on the given sql.""" # Set up the config to only use the rule we are testing. cfg = FluffConfig(configs=configs, overrides={"rules": code}) # Lint it using the current config (while in fix mode) linted = Linter(config=cfg).lint_string(sql, fix=True) lerrs = linted.get_violations() print(f"Errors Found: {lerrs}") for e in lerrs: if e.desc().startswith("Unexpected exception"): pytest.fail(f"Linter failed with {e.desc()}") parse_errors = list(filter(lambda v: type(v) == SQLParseError, lerrs)) if parse_errors: pytest.fail(f"Found the following parse errors in test case: {parse_errors}") if not any(v.rule.code == code for v in lerrs): pytest.fail( f"No {code} failures found in query which should fail.", pytrace=False, ) if line_numbers: actual_line_numbers = [e.line_no for e in lerrs] if line_numbers != actual_line_numbers: pytest.fail( "Expected errors on lines {}, but got errors on lines {}".format( line_numbers, actual_line_numbers ) ) # The query should already have been fixed if possible so just return the raw. return linted.tree.raw
def test_rule_set_return_informative_error_when_rule_not_registered(): """Assert that a rule that throws an exception returns it as a validation.""" cfg = FluffConfig(overrides={"dialect": "ansi"}) with pytest.raises(ValueError) as e: get_rule_from_set("L000", config=cfg) e.match("'L000' not in")
def test__rules__std_file(rule, path, violations): """Test the linter finds the given errors in (and only in) the right places.""" assert_rule_raises_violations_in_file( rule=rule, fpath=path, violations=violations, fluff_config=FluffConfig(overrides=dict(rules=rule)), )
def test__rules__std_file_dbt(rule, path, violations, in_dbt_project_dir): # noqa """Test the linter finds the given errors in (and only in) the right places (DBT).""" assert_rule_raises_violations_in_file( rule=rule, fpath=path, violations=violations, fluff_config=FluffConfig(configs=DBT_FLUFF_CONFIG, overrides=dict(rules=rule)), )
def test__rule_test_case(test_case): """Run the tests.""" res = rules__test_helper(test_case) if res is not None and res != test_case.fail_str: cfg = FluffConfig(configs=test_case.configs) rule = get_rule_from_set(test_case.rule, config=cfg) assert is_fix_compatible( rule ), f'Rule {test_case.rule} returned fixes but does not specify "@document_fix_compatible".'
def test__rule_test_case(test_case, caplog): """Run the tests.""" with caplog.at_level(logging.DEBUG, logger="sqlfluff.rules"): res = rules__test_helper(test_case) if res is not None and res != test_case.fail_str: cfg = FluffConfig(configs=test_case.configs) rule = get_rule_from_set(test_case.rule, config=cfg) assert is_fix_compatible( rule ), f'Rule {test_case.rule} returned fixes but does not specify "@document_fix_compatible".'
def test_rule_exception_is_caught_to_validation(): """Assert that a rule that throws an exception on _eval returns it as a validation.""" @std_rule_set.register class Rule_LXXX(BaseCrawler): """Rule that throws an exception.""" def _eval(self, segment, parent_stack, **kwargs): raise Exception( "Catch me or I'll deny any linting results from you") linter = Linter(config=FluffConfig(overrides=dict(rules="LXXX"))) assert linter.lint_string("select 1").check_tuples() == [("LXXX", 1, 1)]
def assert_rule_pass_in_sql(code, sql, configs=None): """Assert that a given rule doesn't fail on the given sql.""" # Configs allows overrides if we want to use them. cfg = FluffConfig(configs=configs) r = get_rule_from_set(code, config=cfg) parsed = Linter(config=cfg).parse_string(sql) if parsed.violations: pytest.fail(parsed.violations[0].desc() + "\n" + parsed.tree.stringify()) print(f"Parsed:\n {parsed.tree.stringify()}") lerrs, _, _, _ = r.crawl(parsed.tree, dialect=cfg.get("dialect_obj")) print(f"Errors Found: {lerrs}") if any(v.rule.code == code for v in lerrs): pytest.fail(f"Found {code} failures in query which should pass.", pytrace=False)
def test__rules__runaway_fail_catch(): """Test that we catch runaway rules.""" runaway_limit = 5 my_query = "SELECT * FROM foo" # Set up the config to only use the rule we are testing. cfg = FluffConfig(overrides={"rules": "T001", "runaway_limit": runaway_limit}) # Lint it using the current config (while in fix mode) linter = Linter(config=cfg, user_rules=[Rule_T001]) # In theory this step should result in an infinite # loop, but the loop limit should catch it. linted = linter.lint_string(my_query, fix=True) # We should have a lot of newlines in there. # The number should equal the runaway limit assert linted.tree.raw.count("\n") == runaway_limit
def test__rules__std_L003_process_raw_stack(generate_test_segments): """Test the _process_raw_stack function. Note: This test probably needs expanding. It doesn't really check enough of the full functionality. """ cfg = FluffConfig() r = get_rule_from_set("L003", config=cfg) test_stack = generate_test_segments(["bar", "\n", " ", "foo", "baar", " \t "]) res = r._process_raw_stack(test_stack) print(res) assert sorted(res.keys()) == [1, 2] assert res[2]["indent_size"] == 5
def test__rules__runaway_fail_catch(): """Test that we catch runaway rules.""" runaway_limit = 5 my_query = "SELECT * FROM foo" # Set up the config to only use the rule we are testing. cfg = FluffConfig(overrides={ "rules": "T001", "runaway_limit": runaway_limit }) # Lint it using the current config (while in fix mode) linter = Linter(config=cfg, user_rules=[Rule_T001]) # In theory this step should result in an infinite # loop, but the loop limit should catch it. linted = linter.lint_string(my_query, fix=True) # When the linter hits the runaway limit, it returns the original SQL tree. assert linted.tree.raw == my_query
def assert_rule_fail_in_sql(code, sql, configs=None): """Assert that a given rule does fail on the given sql.""" # Set up the config to only use the rule we are testing. cfg = FluffConfig(configs=configs, overrides={"rules": code}) # Lint it using the current config (while in fix mode) linted = Linter(config=cfg).lint_string(sql, fix=True) lerrs = linted.get_violations() print("Errors Found: {0}".format(lerrs)) parse_errors = list(filter(lambda v: type(v) == SQLParseError, lerrs)) if parse_errors: pytest.fail(f"Found the following parse errors in test case: {parse_errors}") if not any(v.rule.code == code for v in lerrs): pytest.fail( "No {0} failures found in query which should fail.".format(code), pytrace=False, ) # The query should already have been fixed if possible so just return the raw. return linted.tree.raw
def test_rule_exception_is_caught_to_validation(): """Assert that a rule that throws an exception returns it as a validation.""" std_rule_set = get_ruleset() @std_rule_set.register class Rule_T000(BaseRule): """Rule that throws an exception.""" groups = ("all", ) crawl_behaviour = RootOnlyCrawler() def _eval(self, segment, parent_stack, **kwargs): raise Exception( "Catch me or I'll deny any linting results from you") linter = Linter( config=FluffConfig(overrides=dict(rules="T000", dialect="ansi")), user_rules=[Rule_T000], ) assert linter.lint_string("select 1").check_tuples() == [("T000", 1, 1)]
def test__rules__std_L007_before(): """Verify that L007 returns the correct error message when before is used.""" sql = """ SELECT a, b FROM foo WHERE a = 1 AND b = 2 """ config = FluffConfig( configs={"rules": {"L007": {"operator_new_lines": "before"}}}, overrides={"dialect": "ansi"}, ) # The sqlfluff.lint API doesn't allow us to pass config so need to do what it does linter = Linter(config=config) result_records = linter.lint_string_wrapped(sql).as_records() result = result_records[0]["violations"] assert "L007" in [r["code"] for r in result] assert before_description in [r["description"] for r in result]
def test__rules__std_L003_process_raw_stack(generate_test_segments, test_elems, result): """Test the _process_raw_stack function. Note: This test probably needs expanding. It doesn't really check enough of the full functionality. """ cfg = FluffConfig() r = get_rule_from_set("L003", config=cfg) test_stack = generate_test_segments(test_elems) res = r._process_raw_stack(test_stack, {}) print(res) # Verify structure assert isinstance(res, dict) assert all(isinstance(k, int) for k in res.keys()) assert all(isinstance(v, dict) for v in res.values()) # Check keys are all present assert all( v.keys() == { "line_no", "templated_line", "line_buffer", "indent_buffer", "indent_size", "indent_balance", "hanging_indent", "clean_indent", } for v in res.values()) for k in res: # For testing purposes, we won't be checking the buffer fields. They're # just too hard to create in the test cases and aren't critical in # determining what course of action to take. Most of the logic uses the # values which we *are* still testing. del res[k]["line_buffer"] del res[k]["indent_buffer"] # We also don't check the "templated_file" flag. These tests don't # exercise that code. del res[k]["templated_line"] assert res == result
def assert_rule_pass_in_sql(code, sql, configs=None): """Assert that a given rule doesn't fail on the given sql.""" # Configs allows overrides if we want to use them. cfg = FluffConfig(configs=configs) r = get_rule_from_set(code, config=cfg) linter = Linter(config=cfg) rendered = linter.render_string(sql, fname="<STR>", config=cfg, encoding="utf-8") parsed = linter.parse_rendered(rendered, recurse=True) if parsed.violations: pytest.fail(parsed.violations[0].desc() + "\n" + parsed.tree.stringify()) print(f"Parsed:\n {parsed.tree.stringify()}") lerrs, _, _, _ = r.crawl(parsed.tree, [], dialect=cfg.get("dialect_obj"), templated_file=rendered[0]) print(f"Errors Found: {lerrs}") if any(v.rule.code == code for v in lerrs): pytest.fail(f"Found {code} failures in query which should pass.", pytrace=False)
def test__rules__std_L007_after(): """Verify orrect error message when after is explicitly used.""" sql = """ SELECT a, b FROM foo WHERE a = 1 AND b = 2 """ config = FluffConfig( configs={"rules": { "L007": { "operator_new_lines": "after" } }}) # The sqlfluff.lint API doesn't allow us to pass config so need to do what it does linter = Linter(config=config) result_records = linter.lint_string_wrapped(sql).as_records() result = result_records[0]["violations"] assert "L007" in [r["code"] for r in result] assert after_description in [r["description"] for r in result]
def assert_rule_fail_in_sql(code, sql, configs=None, line_numbers=None): """Assert that a given rule does fail on the given sql.""" print("# Asserting Rule Fail in SQL") # Set up the config to only use the rule we are testing. overrides = {"rules": code} if configs is None or "core" not in configs or "dialect" not in configs[ "core"]: overrides["dialect"] = "ansi" cfg = FluffConfig(configs=configs, overrides=overrides) # Lint it using the current config (while in fix mode) linted = Linter(config=cfg).lint_string(sql, fix=True) lerrs = linted.get_violations() print("Errors Found:") for e in lerrs: print(" " + repr(e)) if e.desc().startswith("Unexpected exception"): pytest.fail(f"Linter failed with {e.desc()}") # pragma: no cover parse_errors = list( filter(lambda v: isinstance(v, (SQLParseError, SQLTemplaterError)), lerrs)) if parse_errors: pytest.fail( f"Found the following parse errors in test case: {parse_errors}") if not any(v.rule.code == code for v in lerrs): print(f"Parsed File:\n{linted.tree.stringify()}") pytest.fail( f"No {code} failures found in query which should fail.", pytrace=False, ) if line_numbers: actual_line_numbers = [e.line_no for e in lerrs] if line_numbers != actual_line_numbers: # pragma: no cover pytest.fail( "Expected errors on lines {}, but got errors on lines {}". format(line_numbers, actual_line_numbers)) fixed, _ = linted.fix_string() return fixed, linted.violations
def test__plugin_default_config_read(): """Test that the example plugin default config is merged into FluffConfig.""" fluff_config = FluffConfig(overrides={"dialect": "ansi"}) # The plugin import order is non-deterministic assert "forbidden_columns" in fluff_config._configs["rules"]["Example_L001"]
def test_improper_configs_are_rejected(rule_config_dict): """Ensure that unsupported configs raise a ValueError.""" config = FluffConfig(configs={"rules": rule_config_dict}) with pytest.raises(ValueError): get_ruleset().get_rulelist(config)