Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions scripts/check_minimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,58 @@ def do_POST(self):
assert report['attempts'] == 0, report
shutil.move(original_tests, target / 'tests.bak')
assert snapshot(target) == prepared

# Original and generated integration tests must coexist, including on
# a cached rerun and after original-test compilation fails.
answer['chain_tests'] = [dict(
attrs=[], prefix=[], oracles=[], codes=[['{', 'assert_eq!(classify(2), 1);', '}']],
can_compile=[{'Ok': None}], repaired=[False])]
candidate.write_text(json.dumps(data, indent=2) + '\n')
integration_command = ['utgen', 'gen', '-p', str(target), '--functions-file', str(selection), '--integration']

def integration_snapshot():
# Cached generation rewrites imports from a HashSet in any order.
return {p: re.sub(rb'(?m)(?:^use [^\n]*\n)+',
lambda m: b''.join(sorted(m[0].splitlines(keepends=True))), content)
if p.startswith('tests/') else content
for p, content in snapshot(target).items()}

for label in ['integration-original-tests', 'integration-original-tests-cached']:
run(label, integration_command)
result = json.loads((target / f'utgen/result/{names[selected]}.json').read_text())
assert result['tests_run'] == result['tests_passed'] == 1, result
original_result = json.loads((target / 'utgen/original_result.json').read_text())
assert [r['function_name'] for r in original_result] == [selected]
assert original_result[0]['lines_covered'], original_result
current = snapshot(target)
assert {p: content for p, content in current.items() if not p.startswith('tests/')} == prepared
assert any(p.startswith('tests/') for p in current)
assert not (target / 'tests.bak2').exists()
integration_prepared = integration_snapshot()
original_file = target / 'tests.bak/original.rs'
original_content = original_file.read_bytes()
original_file.write_bytes(original_content + b'\ncompile_error!("original-test failure fixture");\n')
failed_prepared = integration_snapshot()
failure = run('integration-original-tests-compile-error', integration_command, expected=1)
assert 'original-test failure fixture' in failure.stderr
assert integration_snapshot() == failed_prepared
assert not (target / 'tests.bak2').exists()
original_file.write_bytes(original_content)
assert integration_snapshot() == integration_prepared
# An existing, even empty, staging directory must not be overwritten.
staging = target / 'tests.bak2'
staging.mkdir()
failure = run('integration-existing-staging', integration_command, expected=1)
assert 'tests.bak2 already exists' in failure.stderr
assert staging.is_dir() and not list(staging.iterdir())
assert integration_snapshot() == integration_prepared
staging.rmdir()
assert not list((target / 'src').rglob('*.bak'))
assert (state['generation'], state['repair']) == requests_before, state
report = json.loads((target / 'utgen/generation/gen-requests.json').read_text())
assert report['attempts'] == 0, report
shutil.rmtree(target / 'tests')
assert snapshot(target) == prepared
(work / 'summary.json').write_text(json.dumps(state, indent=2) + '\n')
print('Minimal analysis/generation/repair/coverage checks passed.', flush=True)
finally:
Expand Down
62 changes: 58 additions & 4 deletions utgen/src/run/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,52 @@ pub enum TestType {
Error,
}

/// Return a temporarily moved test directory on normal completion or unwind.
struct MovedTestDirectory {
original: PathBuf,
temporary: PathBuf,
restored: bool,
}

impl MovedTestDirectory {
fn new(original: &Path, temporary: &Path) -> std::io::Result<Self> {
if temporary.exists() {
return Err(std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!(
"{} already exists; preserve it and use a fresh working copy",
temporary.display()
),
));
}
fs::rename(original, temporary)?;
Ok(Self {
original: original.to_owned(),
temporary: temporary.to_owned(),
restored: false,
})
}

fn finish(mut self) -> std::io::Result<()> {
fs::rename(&self.temporary, &self.original)?;
self.restored = true;
Ok(())
}
}

impl Drop for MovedTestDirectory {
fn drop(&mut self) {
if !self.restored {
if let Err(error) = fs::rename(&self.temporary, &self.original) {
error!(
"Cannot restore {}: {error}; directory retained at {}",
self.original.display(), self.temporary.display()
);
}
}
}
}

pub fn run_test(
project_dir: &Path,
work_path: &Path,
Expand Down Expand Up @@ -418,14 +464,14 @@ pub fn gen_test_rate(
// let _ = target_clean(work_dir);
let test_path = work_dir.join("tests");
let bak_test_path2 = work_dir.join("tests.bak2");
fs::rename(&test_path, &bak_test_path2).unwrap();
let generated_tests = MovedTestDirectory::new(&test_path, &bak_test_path2)
.expect("cannot temporarily move generated tests");
gen_codes_lines_and_branches_covered(
project_dir,
work_dir,
&test_gen_infos,
&mut test_rate_infos,
);
fs::rename(&bak_test_path2, &test_path).unwrap();
dump_result_for_test_rate_infos(
&test_rate_infos,
project_dir,
Expand All @@ -437,6 +483,7 @@ pub fn gen_test_rate(
if is_pre {
gen_coverage_rate_for_original_tests(project_dir, work_dir, &test_gen_infos);
}
generated_tests.finish().expect("cannot restore generated tests");
}
}

Expand Down Expand Up @@ -1410,7 +1457,13 @@ fn gen_coverage_rate_for_original_tests(
let bak_test_dir = work_dir.join("tests.bak");
if exists(&bak_test_dir).unwrap() {
info!("Generate coverage rate for original tests");
fs::rename(&bak_test_dir, &test_dir).unwrap();
// Import checks can leave an empty tests/ directory in unit mode.
if test_dir.exists() {
fs::remove_dir(&test_dir)
.expect("tests directory must be empty before restoring original tests");
}
let original_tests = MovedTestDirectory::new(&bak_test_dir, &test_dir)
.expect("cannot restore original tests for coverage");
// let _ = target_clean(work_dir);
let test_type = TestType::CoverageRate;
run_test(project_dir, work_dir, test_type, true, false);
Expand Down Expand Up @@ -1443,7 +1496,8 @@ fn gen_coverage_rate_for_original_tests(
);
original_test_rate_infos.push(original_test_rate_info);
}
fs::rename(&test_dir, &bak_test_dir).unwrap();
original_tests.finish()
.expect("cannot return original tests to their backup");
dump_result_for_original_test_rate_infos(&original_test_rate_infos, project_dir, work_dir);
let coverage_file_path = work_dir.join("coverage.xml");
let lcov_file_path = work_dir.join("coverage.json");
Expand Down
Loading