def test_linter_noqa_disable(): """Test "noqa" comments can be disabled via the config.""" lntr_noqa_enabled = Linter( config=FluffConfig( overrides={ "rules": "L012", } ) ) lntr_noqa_disabled = Linter( config=FluffConfig( overrides={ "disable_noqa": True, "rules": "L012", } ) ) # This query raises L012, but it is being suppressed by the inline noqa comment. # We can ignore this comment by setting disable_noqa = True in the config # or by using the --disable-noqa flag in the CLI. sql = """ SELECT col_a a --noqa: L012 FROM foo """ # Verify that noqa works as expected with disable_noqa = False (default). result_noqa_enabled = lntr_noqa_enabled.lint_string(sql) violations_noqa_enabled = result_noqa_enabled.get_violations() assert len(violations_noqa_enabled) == 0 # Verify that noqa comment is ignored with disable_noqa = True. result_noqa_disabled = lntr_noqa_disabled.lint_string(sql) violations_noqa_disabled = result_noqa_disabled.get_violations() assert len(violations_noqa_disabled) == 1 assert violations_noqa_disabled[0].rule.code == "L012"
def test__linter__lint_string_vs_file(path): """Test the linter finds the same things on strings and files.""" with open(path) as f: sql_str = f.read() lntr = Linter() assert (lntr.lint_string(sql_str).check_tuples() == lntr.lint_path( path).check_tuples())
def test_linter_noqa_with_templating(): """Similar to test_linter_noqa, but uses templating (Jinja).""" lntr = Linter( config=FluffConfig( overrides={ "dialect": "bigquery", # Use bigquery to allow hash comments. "templater": "jinja", "rules": "L016", } ) ) sql = "\n" '"{%- set a_var = ["1", "2"] -%}\n' "SELECT\n" " this_is_just_a_very_long_line_for_demonstration_purposes_of_a_bug_involving_" "templated_sql_files, --noqa: L016\n" " this_is_not_so_big a, --Inline comment --noqa: L012\n" " this_is_not_so_big b, /* Block comment */ --noqa: L012\n" " this_is_not_so_big c, # hash comment --noqa: L012\n" " this_is_just_a_very_long_line_for_demonstration_purposes_of_a_bug_involving_" "templated_sql_files, --noqa: L01*\n" "FROM\n" " a_table\n" " " result = lntr.lint_string(sql) assert not result.get_violations()
def test_linter_noqa(): """Test "noqa" feature at the higher "Linter" level.""" lntr = Linter(config=FluffConfig(overrides={ "rules": "L012", })) sql = """ SELECT col_a a, col_b b, --noqa: disable=L012 col_c c, col_d d, --noqa: enable=L012 col_e e, col_f f, col_g g, --noqa col_h h, col_i i, --noqa:L012 col_j j, col_k k, --noqa:L013 col_l l, col_m m, col_n n, --noqa: disable=all col_o o, col_p p --noqa: enable=all FROM foo """ result = lntr.lint_string(sql) violations = result.get_violations() assert {3, 6, 7, 8, 10, 12, 13, 14, 15, 18} == {v.line_no for v in violations}
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 test_linter_noqa_prs(): """Test "noqa" feature to ignore PRS at the higher "Linter" level.""" lntr = Linter(dialect="ansi") sql = "SELEC * FROM foo -- noqa: PRS\n" result = lntr.lint_string(sql) violations = result.get_violations() assert not violations
def test_non_selects_unparseable(raw: str) -> None: """Test that non-SELECT commands are not parseable.""" cfg = FluffConfig(configs={"core": {"dialect": "soql"}}) lnt = Linter(config=cfg) result = lnt.lint_string(raw) assert len(result.violations) == 1 assert isinstance(result.violations[0], SQLParseError)
def test__templated_sections_do_not_raise_lint_error(in_dbt_project_dir): # noqa """Test that the dbt test has only a new line lint error.""" lntr = Linter(config=FluffConfig(configs=DBT_FLUFF_CONFIG)) lnt = lntr.lint_string(fname="tests/test.sql") print(lnt.violations) assert len(lnt.violations) == 1 # Newlines are removed by dbt templater assert lnt.violations[0].rule.code == "L009"
def test_space_is_not_reserved(raw): """Ensure that SPACE is not treated as reserved.""" cfg = FluffConfig(configs={ "core": { "exclude_rules": "L009,L016,L031", "dialect": "postgres" } }) lnt = Linter(config=cfg) result = lnt.lint_string(raw) assert result.num_violations() == 0
def test_epoch_datetime_unit(raw): """Test the EPOCH keyword for postgres dialect.""" # Don't test for new lines or capitalisation cfg = FluffConfig(configs={ "core": { "exclude_rules": "L009,L016,L036", "dialect": "postgres" } }) lnt = Linter(config=cfg) result = lnt.lint_string(raw) assert result.num_violations() == 0
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 test_linter_noqa_template_errors(): """Similar to test_linter_noqa, but uses templating (Jinja).""" lntr = Linter(config=FluffConfig(overrides={ "templater": "jinja", "dialect": "ansi", })) sql = """select * --noqa: TMP from raw where balance_date >= {{ execution_date - macros.timedelta() }} --noqa: TMP """ result = lntr.lint_string(sql) assert not result.get_violations()
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_linter_noqa_tmp(): """Test "noqa" feature to ignore TMP at the higher "Linter" level.""" lntr = Linter(config=FluffConfig(overrides={ "exclude_rules": "L050", "dialect": "ansi", })) sql = """ SELECT {{ col_a }} AS a -- noqa: TMP,PRS FROM foo; """ result = lntr.lint_string(sql) print(result.tree.stringify()) violations = result.get_violations() assert not violations
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 test_linter_noqa_with_templating(): """Similar to test_linter_noqa, but uses templating (Jinja).""" lntr = Linter(config=FluffConfig(overrides={ "templater": "jinja", "rules": "L016", })) sql = """ {%- set a_var = ["1", "2"] -%} SELECT this_is_just_a_very_long_line_for_demonstration_purposes_of_a_bug_involving_templated_sql_files, --noqa: L016 this_is_not_so_big FROM a_table """ result = lntr.lint_string(sql) assert not result.get_violations()
def test_linter_noqa(): """Test "noqa" feature at the higher "Linter" level.""" lntr = Linter( config=FluffConfig( overrides={ "dialect": "bigquery", # Use bigquery to allow hash comments. "rules": "L012, L019", } ) ) sql = """ SELECT col_a a, col_b b, --noqa: disable=L012 col_c c, col_d d, --noqa: enable=L012 col_e e, col_f f, col_g g, --noqa col_h h, col_i i, --noqa:L012 col_j j, col_k k, --noqa:L013 col_l l, col_m m, col_n n, --noqa: disable=all col_o o, col_p p, --noqa: enable=all col_q q, --Inline comment --noqa: L012 col_r r, /* Block comment */ --noqa: L012 col_s s # hash comment --noqa: L012 -- We trigger both L012 (implicit aliasing) -- and L019 (leading commas) here to -- test glob ignoring of multiple rules. , col_t t --noqa: L01* , col_u u -- Some comment --noqa: L01* , col_v v -- We can ignore both L012 and L019 -- noqa: L01[29] FROM foo """ result = lntr.lint_string(sql) violations = result.get_violations() assert {3, 6, 7, 8, 10, 12, 13, 14, 15, 18} == {v.line_no for v in violations}
def test__rules__std_L009_and_L052_interaction() -> None: """Test interaction between L009 and L052 doesn't stop L052 from being applied.""" # Test sql with no final newline and no final semicolon. sql = "SELECT foo FROM bar" # Ensure final semicolon requirement is active. cfg = FluffConfig() cfg.set_value(config_path=["rules", "L052", "require_final_semicolon"], val=True) linter = Linter(config=cfg) # Return linted/fixed file. linted_file = linter.lint_string(sql, fix=True) # Check expected lint errors are raised. assert set([v.rule.code for v in linted_file.violations]) == {"L009", "L052"} # Check file is fixed. assert linted_file.fix_string()[0] == "SELECT foo FROM bar;\n"
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_linter_noqa_prs(): """Test "noqa" feature to ignore PRS at the higher "Linter" level.""" lntr = Linter( config=FluffConfig( overrides={ "dialect": "bigquery", "exclude_rules": "L050", } ) ) sql = """ CREATE TABLE IF NOT EXISTS Test.events (userID STRING, eventName STRING, eventID INTEGER, device STRUCT < mobileBrandName STRING, -- noqa: PRS mobileModelName STRING>); Insert into Test.events VALUES ("1","abc",123,STRUCT("htc","10")); """ result = lntr.lint_string(sql) violations = result.get_violations() assert not violations
def test__linter__get_violations_filter_rules(rules, num_violations): """Test filtering violations by which rules were violated.""" lntr = Linter() lint_result = lntr.lint_string("select a, b FROM tbl c order BY d") assert len(lint_result.get_violations(rules=rules)) == num_violations