mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
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.
86 lines
2.2 KiB
Python
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
|