Files
codeql/swift/codegen/test/utils.py
Paolo Tranquilli 2cd58817d7 Swift: skip QL code generation on untouched files
This is a developer QoL improvement, where running codegen will skip
writing (and especially formatting) any files that were not changed.

**Why?** While code generation in itself was pretty much instant, QL
formatting of generated code was starting to take a long time. This made
unconditionally running codegen quite annoying, for example before each
test run as part of an IDE workflow or as part of the pre-commit hook.

**How?** This was not completely straightforward as we could not work
with the contents of the file prior to code generation as that was
already post-processed by the QL formatting, so we had no chance of
comparing the output of template rendering with that. We therefore store
the hashes of the files _prior_ to QL formatting in a checked-in file
(`swift/ql/.generated.list`). We can therefore load those hashes at
the beginning of code generation, use them to compare the template
rendering output and update them in this special registry file.

**What else?** We also extend this mechanism to detect accidental
modification of generated files in a more robust way. Before this patch,
we were doing it with a rough regexp based heuristic. Now, we just store
the hashes of the files _after_ QL formatting in the same checked file,
so we can check that and stop generation if a generated file was
modified, or a stub was modified without removing the `// generated`
header.
2022-11-18 16:56:01 +01:00

86 lines
2.2 KiB
Python

import pathlib
from unittest import mock
import pytest
from swift.codegen.lib import render, schema, paths
schema_dir = pathlib.Path("a", "dir")
schema_file = schema_dir / "schema.py"
dbscheme_file = pathlib.Path("another", "dir", "test.dbscheme")
def write(out, contents=""):
out.parent.mkdir(parents=True, exist_ok=True)
with open(out, "w") as out:
out.write(contents)
@pytest.fixture
def renderer():
return mock.Mock(spec=render.Renderer)
@pytest.fixture
def render_manager(renderer):
ret = mock.Mock(spec=render.RenderManager)
ret.__enter__ = mock.Mock(return_value=ret)
ret.__exit__ = mock.Mock(return_value=None)
ret.is_customized_stub.return_value = False
return ret
@pytest.fixture
def opts():
ret = mock.MagicMock()
ret.swift_dir = paths.swift_dir
return ret
@pytest.fixture(autouse=True)
def override_paths(tmp_path):
with (mock.patch("swift.codegen.lib.paths.swift_dir", tmp_path),
mock.patch("swift.codegen.lib.paths.exe_file", tmp_path / "exe"),
):
yield
@pytest.fixture
def input(opts, tmp_path):
opts.schema = tmp_path / schema_file
with mock.patch("swift.codegen.lib.schema.load_file") as load_mock:
load_mock.return_value = schema.Schema([])
yield load_mock.return_value
assert load_mock.mock_calls == [
mock.call(opts.schema),
], load_mock.mock_calls
@pytest.fixture
def dbscheme_input(opts, tmp_path):
opts.dbscheme = tmp_path / dbscheme_file
with mock.patch("swift.codegen.lib.dbscheme.iterload") as load_mock:
load_mock.entities = []
load_mock.side_effect = lambda _: load_mock.entities
yield load_mock
assert load_mock.mock_calls == [
mock.call(opts.dbscheme),
], load_mock.mock_calls
def run_generation(generate, opts, renderer):
output = {}
renderer.render.side_effect = lambda data, out: output.__setitem__(out, data)
generate(opts, renderer)
return output
def run_managed_generation(generate, opts, renderer, render_manager):
output = {}
renderer.manage.side_effect = (render_manager,)
render_manager.render.side_effect = lambda data, out: output.__setitem__(out, data)
generate(opts, renderer)
return output