mirror of
https://github.com/trailofbits/algo.git
synced 2026-08-20 22:52:34 +02:00
* ci: modernize tooling with prek, ty, and security scanning Migrate from pre-commit to prek (Rust-native, faster hooks) and add comprehensive CI improvements for code quality and security. Changes: - Replace pre-commit with prek for git hooks - Add ty type checker (Rust-based, replaces mypy) - Expand ruff rules: security (S), simplify (SIM), commented code (ERA) - Add pip-audit workflow for Python dependency CVE scanning - Add actionlint and zizmor for GitHub Actions linting/security - Add ruff format check to CI - Enable stricter ansible-lint rules (no-changed-when, risky-file-permissions) - Remove obsolete Claude workflow files - Apply ruff formatting fixes to test files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ci): resolve actionlint install and ty type errors - Use actionlint's official install script instead of broken URL pattern - Exclude test mock modules from ty type checking - Run workflows on push only for main/master to avoid duplicate PR runs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ci): use glob pattern for actionlint, exclude all tests from ty - actionlint requires *.yml glob, not directory path - Exclude all tests from ty type checking (test code has looser typing) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ci): quote shell variables to fix shellcheck warnings Fix SC2046/SC2086 warnings in workflow scripts: - Quote $(uname -r) in apt-get install - Quote $(pwd) in docker volume mount - Quote $existing in gh issue comment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ci): move key-order[task] to warn_list Too many existing violations in the codebase to enable as error. Move to warn_list for gradual fixes over time. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
121 lines
5.4 KiB
Python
121 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test that Ansible variables produce proper boolean types, not strings.
|
|
This prevents issues with Ansible 12.0.0's strict type checking.
|
|
"""
|
|
|
|
import jinja2
|
|
|
|
|
|
def render_template(template_str, variables=None):
|
|
"""Render a Jinja2 template with given variables."""
|
|
env = jinja2.Environment()
|
|
template = env.from_string(template_str)
|
|
return template.render(variables or {})
|
|
|
|
|
|
class TestBooleanVariables:
|
|
"""Test that critical variables produce actual booleans."""
|
|
|
|
def test_ipv6_support_produces_boolean(self):
|
|
"""Ensure ipv6_support produces boolean, not string 'true'/'false'."""
|
|
# Test with gateway defined (should be boolean True)
|
|
template = "{{ ansible_default_ipv6['gateway'] is defined }}"
|
|
vars_with_gateway = {"ansible_default_ipv6": {"gateway": "fe80::1"}}
|
|
result = render_template(template, vars_with_gateway)
|
|
assert result == "True" # Jinja2 renders boolean True as string "True"
|
|
|
|
# Test without gateway (should be boolean False)
|
|
vars_no_gateway = {"ansible_default_ipv6": {}}
|
|
result = render_template(template, vars_no_gateway)
|
|
assert result == "False" # Jinja2 renders boolean False as string "False"
|
|
|
|
# The key is that we're NOT producing string literals "true" or "false"
|
|
bad_template = "{% if ansible_default_ipv6['gateway'] is defined %}true{% else %}false{% endif %}"
|
|
result_bad = render_template(bad_template, vars_no_gateway)
|
|
assert result_bad == "false" # This is a string literal, not a boolean
|
|
|
|
# Verify our fix doesn't produce string literals
|
|
assert result != "false" # Our fix produces "False" (from boolean), not "false" (string literal)
|
|
|
|
def test_algo_variables_boolean_fallbacks(self):
|
|
"""Ensure algo_* variables produce booleans in their fallback cases."""
|
|
# Test the fixed template (produces boolean)
|
|
good_template = "{% if var is defined %}{{ var | bool }}{%- else %}{{ false }}{% endif %}"
|
|
result_good = render_template(good_template, {})
|
|
assert result_good == "False" # Boolean False renders as "False"
|
|
|
|
# Test the old broken template (produces string)
|
|
bad_template = "{% if var is defined %}{{ var | bool }}{%- else %}false{% endif %}"
|
|
result_bad = render_template(bad_template, {})
|
|
assert result_bad == "false" # String literal "false"
|
|
|
|
# Verify they're different
|
|
assert result_good != result_bad
|
|
assert result_good == "False" and result_bad == "false"
|
|
|
|
def test_boolean_filter_on_strings(self):
|
|
"""Test that the bool filter correctly converts string values."""
|
|
# Since we can't test Ansible's bool filter directly in Jinja2,
|
|
# we test the pattern we're using in our templates
|
|
|
|
# Test that our templates don't use raw string "true"/"false"
|
|
# which would fail in Ansible 12
|
|
bad_pattern = "{%- else %}false{% endif %}"
|
|
good_pattern = "{%- else %}{{ false }}{% endif %}"
|
|
|
|
# The bad pattern produces a string literal
|
|
result_bad = render_template("{% if var is defined %}something" + bad_pattern, {})
|
|
assert "false" in result_bad # String literal
|
|
|
|
# The good pattern produces a boolean value
|
|
result_good = render_template("{% if var is defined %}something" + good_pattern, {})
|
|
assert "False" in result_good # Boolean False rendered as "False"
|
|
|
|
def test_ansible_12_conditional_compatibility(self):
|
|
"""
|
|
Test that our fixes work with Ansible 12's strict type checking.
|
|
This simulates what Ansible 12 will do with our variables.
|
|
"""
|
|
# Our fixed template - produces actual boolean
|
|
fixed_ipv6 = "{{ ansible_default_ipv6['gateway'] is defined }}"
|
|
fixed_algo = "{% if var is defined %}{{ var | bool }}{%- else %}{{ false }}{% endif %}"
|
|
|
|
# Simulate the boolean value in a conditional context
|
|
# In Ansible 12, this would fail if it's a string "true"/"false"
|
|
vars_with_gateway = {"ansible_default_ipv6": {"gateway": "fe80::1"}}
|
|
ipv6_result = render_template(fixed_ipv6, vars_with_gateway)
|
|
|
|
# The result should be "True" (boolean rendered), not "true" (string literal)
|
|
assert ipv6_result == "True"
|
|
assert ipv6_result != "true"
|
|
|
|
# Test algo variable fallback
|
|
algo_result = render_template(fixed_algo, {})
|
|
assert algo_result == "False"
|
|
assert algo_result != "false"
|
|
|
|
def test_regression_no_string_booleans(self):
|
|
"""
|
|
Regression test: ensure we never produce string literals 'true' or 'false'.
|
|
This is what breaks Ansible 12.0.0.
|
|
"""
|
|
# These patterns should NOT appear in our fixed code
|
|
bad_patterns = [
|
|
"{}true{}",
|
|
"{}false{}",
|
|
"{%- else %}true{% endif %}",
|
|
"{%- else %}false{% endif %}",
|
|
]
|
|
|
|
# Test that our fixed templates don't produce string boolean literals
|
|
fixed_template = "{{ ansible_default_ipv6['gateway'] is defined }}"
|
|
for _pattern in bad_patterns:
|
|
assert "true" not in fixed_template.replace(" ", "")
|
|
assert "false" not in fixed_template.replace(" ", "")
|
|
|
|
# Test algo variable fix
|
|
fixed_algo = "{% if var is defined %}{{ var | bool }}{%- else %}{{ false }}{% endif %}"
|
|
assert "{}false{}" not in fixed_algo.replace(" ", "")
|
|
assert "{{ false }}" in fixed_algo
|