Integrate the ignored-workspace corpus into verification
Give the Git Sentry launch shape one Rust owner, update the physical corpus to supply every authority, and make the default repository gate distinguish unavailable physical prerequisites from corpus failures.
This commit is contained in:
parent
bda342890f
commit
a0055f67f1
6 changed files with 210 additions and 17 deletions
|
|
@ -1,8 +1,7 @@
|
|||
use std::env;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use lazaret_ignored_workspace::{GitSentry, GitSentryConfiguration};
|
||||
use lazaret_ignored_workspace::{GitSentry, GitSentryLaunch};
|
||||
|
||||
fn main() {
|
||||
if let Err(error) = run() {
|
||||
|
|
@ -12,18 +11,8 @@ fn main() {
|
|||
}
|
||||
|
||||
fn run() -> io::Result<()> {
|
||||
let arguments = env::args_os().skip(1).collect::<Vec<_>>();
|
||||
if arguments.len() != 4 {
|
||||
return Err(io::Error::other(
|
||||
"usage: lazaret-git-sentry MOUNTED_WORKSPACE HOST_GIT_COMMON_DIRECTORY TRANSIENT_HOOKS TRANSIENT_CONFIG",
|
||||
));
|
||||
}
|
||||
let sentry = GitSentry::install(&GitSentryConfiguration {
|
||||
mounted_workspace: PathBuf::from(&arguments[0]),
|
||||
host_git_common_directory: PathBuf::from(&arguments[1]),
|
||||
transient_hooks: PathBuf::from(&arguments[2]),
|
||||
transient_config: PathBuf::from(&arguments[3]),
|
||||
})?;
|
||||
let launch = GitSentryLaunch::parse(env::args_os().skip(1).collect())?;
|
||||
let sentry = GitSentry::install(&launch.configuration())?;
|
||||
println!("ready");
|
||||
io::stdout().flush()?;
|
||||
io::stdin().read_to_end(&mut Vec::new())?;
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ mod sentry;
|
|||
pub use classifier::{Classification, ClassificationSnapshot, GitClassifier, git_common_directory};
|
||||
pub use daemon::{PRIVATE_ROUTER_ROLE, dispatch_private_router};
|
||||
pub use filesystem::{IgnoredWorkspace, MountConfiguration};
|
||||
pub use sentry::{GitSentry, GitSentryConfiguration};
|
||||
pub use sentry::{GitSentry, GitSentryConfiguration, GitSentryLaunch};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -18,6 +19,53 @@ pub struct GitSentryConfiguration {
|
|||
pub transient_config: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct GitSentryLaunch {
|
||||
arguments: [PathBuf; 4],
|
||||
}
|
||||
|
||||
impl GitSentryLaunch {
|
||||
#[must_use]
|
||||
pub fn new(configuration: GitSentryConfiguration) -> Self {
|
||||
Self {
|
||||
arguments: [
|
||||
configuration.mounted_workspace,
|
||||
configuration.host_git_common_directory,
|
||||
configuration.transient_hooks,
|
||||
configuration.transient_config,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
/// Refuses any argument count other than the exact Git Sentry launch shape.
|
||||
pub fn parse(arguments: Vec<OsString>) -> io::Result<Self> {
|
||||
let arguments: [OsString; 4] = arguments.try_into().map_err(|_| {
|
||||
io::Error::other(
|
||||
"usage: lazaret-git-sentry MOUNTED_WORKSPACE HOST_GIT_COMMON_DIRECTORY TRANSIENT_HOOKS TRANSIENT_CONFIG",
|
||||
)
|
||||
})?;
|
||||
Ok(Self {
|
||||
arguments: arguments.map(PathBuf::from),
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn configuration(&self) -> GitSentryConfiguration {
|
||||
GitSentryConfiguration {
|
||||
mounted_workspace: self.arguments[0].clone(),
|
||||
host_git_common_directory: self.arguments[1].clone(),
|
||||
transient_hooks: self.arguments[2].clone(),
|
||||
transient_config: self.arguments[3].clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn arguments(&self) -> [OsString; 4] {
|
||||
self.arguments.clone().map(PathBuf::into_os_string)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GitSentry {
|
||||
config: InstalledMount,
|
||||
hooks: InstalledMount,
|
||||
|
|
@ -266,3 +314,42 @@ fn require_regular_file(file: &File) -> io::Result<()> {
|
|||
Err(io::Error::other("host Git config is not a regular file"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::{GitSentryConfiguration, GitSentryLaunch};
|
||||
|
||||
fn configuration() -> GitSentryConfiguration {
|
||||
GitSentryConfiguration {
|
||||
mounted_workspace: PathBuf::from("/workspace"),
|
||||
host_git_common_directory: PathBuf::from("/host/.git"),
|
||||
transient_hooks: PathBuf::from("/transient/hooks"),
|
||||
transient_config: PathBuf::from("/transient/config"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launch_arguments_round_trip_through_the_binary_parser() {
|
||||
let launch = GitSentryLaunch::new(configuration());
|
||||
let parsed = GitSentryLaunch::parse(launch.arguments().into()).unwrap();
|
||||
|
||||
assert_eq!(parsed, launch);
|
||||
assert_eq!(
|
||||
parsed.configuration().transient_config,
|
||||
PathBuf::from("/transient/config")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launch_parser_requires_every_ordered_authority() {
|
||||
for count in [0, 1, 2, 3, 5] {
|
||||
let arguments = (0..count)
|
||||
.map(|index| OsString::from(format!("/{index}")))
|
||||
.collect();
|
||||
assert!(GitSentryLaunch::parse(arguments).is_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
import copy
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
|
|
@ -15,6 +16,12 @@ LOADER = importlib.machinery.SourceFileLoader(
|
|||
SPEC = importlib.util.spec_from_loader(LOADER.name, LOADER)
|
||||
VERIFY = importlib.util.module_from_spec(SPEC)
|
||||
LOADER.exec_module(VERIFY)
|
||||
CORPUS_LOADER = importlib.machinery.SourceFileLoader(
|
||||
"ignored_workspace_verifier", str(ROOT / "tools/ignored_workspace_verifier.py")
|
||||
)
|
||||
CORPUS_SPEC = importlib.util.spec_from_loader(CORPUS_LOADER.name, CORPUS_LOADER)
|
||||
CORPUS = importlib.util.module_from_spec(CORPUS_SPEC)
|
||||
CORPUS_LOADER.exec_module(CORPUS)
|
||||
|
||||
|
||||
class VerificationTests(unittest.TestCase):
|
||||
|
|
@ -414,6 +421,59 @@ class VerificationTests(unittest.TestCase):
|
|||
with self.assertRaisesRegex(VERIFY.VerificationError, "physical test exited 1"):
|
||||
VERIFY.run_physical(["physical"])
|
||||
|
||||
def test_ignored_workspace_corpus_inventory_has_an_executable_owner(self) -> None:
|
||||
inventory = json.loads(
|
||||
(
|
||||
ROOT / "tests/fixtures/ignored-workspace-v1/corpus.json"
|
||||
).read_text()
|
||||
)["cases"]
|
||||
implemented = set(CORPUS.CASE_GROUPS) | {"linked-worktree-index", "git-sentry"}
|
||||
self.assertEqual({case["id"] for case in inventory}, implemented)
|
||||
|
||||
def test_git_sentry_launch_uses_every_production_authority(self) -> None:
|
||||
self.assertEqual(
|
||||
CORPUS.git_sentry_command(
|
||||
"sentry", "workspace", "common", "hooks", "config"
|
||||
),
|
||||
["sentry", "workspace", "common", "hooks", "config"],
|
||||
)
|
||||
|
||||
def test_ignored_workspace_prerequisite_absence_is_specific(self) -> None:
|
||||
with mock.patch.object(CORPUS.sys, "platform", "darwin"):
|
||||
self.assertEqual(CORPUS.unavailable_physical_prerequisite(), "Linux is required")
|
||||
with (
|
||||
mock.patch.object(CORPUS.sys, "platform", "linux"),
|
||||
mock.patch.object(CORPUS.shutil, "which", return_value=None),
|
||||
):
|
||||
self.assertEqual(
|
||||
CORPUS.unavailable_physical_prerequisite(), "git is unavailable"
|
||||
)
|
||||
|
||||
def test_default_verifier_runs_the_complete_ignored_workspace_corpus(self) -> None:
|
||||
binaries = {
|
||||
"lazaret-ignored-workspace": Path("/build/router"),
|
||||
"lazaret-git-sentry": Path("/build/sentry"),
|
||||
}
|
||||
commands = []
|
||||
with (
|
||||
mock.patch.object(VERIFY, "build_workspace_binaries", return_value=binaries),
|
||||
mock.patch.object(
|
||||
VERIFY,
|
||||
"run_physical",
|
||||
side_effect=lambda command, cwd=VERIFY.ROOT: commands.append(command),
|
||||
),
|
||||
):
|
||||
VERIFY.run_ignored_workspace_corpus({})
|
||||
[command] = commands
|
||||
self.assertEqual(
|
||||
command[0:2],
|
||||
[VERIFY.sys.executable, str(ROOT / "tools/ignored_workspace_verifier.py")],
|
||||
)
|
||||
self.assertIn("corpus", command)
|
||||
self.assertIn(str(binaries["lazaret-ignored-workspace"]), command)
|
||||
self.assertIn(str(binaries["lazaret-git-sentry"]), command)
|
||||
self.assertNotIn("--case", command)
|
||||
|
||||
def test_advisory_refresh_checks_versions_before_mutation(self) -> None:
|
||||
events = []
|
||||
original_versions = VERIFY.check_tool_versions
|
||||
|
|
|
|||
|
|
@ -576,11 +576,19 @@ def sentry_case(case_id, fixture, sentry_binary):
|
|||
commit(fixture)
|
||||
transient = fixture.root / "transient-hooks"
|
||||
transient.mkdir()
|
||||
transient_config = fixture.root / "transient-config"
|
||||
transient_config.touch()
|
||||
write(fixture.host / ".git/hooks/host-sentinel", b"host")
|
||||
host_config = (fixture.host / ".git/config").read_bytes()
|
||||
with fixture.start():
|
||||
process = subprocess.Popen(
|
||||
[sentry_binary, fixture.mount, fixture.host / ".git", transient],
|
||||
git_sentry_command(
|
||||
sentry_binary,
|
||||
fixture.mount,
|
||||
fixture.host / ".git",
|
||||
transient,
|
||||
transient_config,
|
||||
),
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
|
|
@ -601,6 +609,10 @@ def sentry_case(case_id, fixture, sentry_binary):
|
|||
require((transient / "sandbox-hook").read_bytes() == b"transient", "transient hook missing")
|
||||
|
||||
|
||||
def git_sentry_command(binary, workspace, common, hooks, config):
|
||||
return [binary, workspace, common, hooks, config]
|
||||
|
||||
|
||||
def invalidation_or_race_case(case_id, fixture):
|
||||
ignored_pair(fixture)
|
||||
with fixture.start():
|
||||
|
|
@ -705,6 +717,24 @@ def run_corpus(arguments):
|
|||
require(all(result["disposition"] == "PASS" for result in results), "one or more corpus cases failed")
|
||||
|
||||
|
||||
def unavailable_physical_prerequisite():
|
||||
if sys.platform != "linux":
|
||||
return "Linux is required"
|
||||
for program in ("git", "unshare", "fusermount3"):
|
||||
if shutil.which(program) is None:
|
||||
return f"{program} is unavailable"
|
||||
fuse = Path("/dev/fuse")
|
||||
if not fuse.exists() or not os.access(fuse, os.R_OK | os.W_OK):
|
||||
return "/dev/fuse is unavailable"
|
||||
probe = command(
|
||||
["unshare", "--user", "--map-root-user", "--mount", "--propagation", "private", "--", "true"],
|
||||
check=False,
|
||||
)
|
||||
if probe.returncode != 0:
|
||||
return "private user and mount namespaces are unavailable"
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
|
@ -717,6 +747,10 @@ def main():
|
|||
corpus.add_argument("--fail-fast", action="store_true")
|
||||
arguments = parser.parse_args()
|
||||
if arguments.command == "corpus":
|
||||
unavailable = unavailable_physical_prerequisite()
|
||||
if unavailable is not None:
|
||||
print(f"SKIP ignored-workspace corpus: {unavailable}", file=sys.stderr)
|
||||
return 77
|
||||
if arguments.case in (None, "git-sentry") and os.environ.get(NAMESPACE_MARKER) != "1":
|
||||
environment = os.environ.copy()
|
||||
environment[NAMESPACE_MARKER] = "1"
|
||||
|
|
@ -737,7 +771,8 @@ def main():
|
|||
environment,
|
||||
)
|
||||
run_corpus(arguments)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main())
|
||||
|
|
|
|||
|
|
@ -618,6 +618,27 @@ def run_rust_tests() -> None:
|
|||
run(list(DOCTEST_COMMAND))
|
||||
|
||||
|
||||
def run_ignored_workspace_corpus(metadata: dict) -> None:
|
||||
binaries = build_workspace_binaries(metadata)
|
||||
with tempfile.TemporaryDirectory(prefix="lazaret-ignored-workspace-corpus-") as directory:
|
||||
root = Path(directory)
|
||||
run_physical(
|
||||
[
|
||||
sys.executable,
|
||||
str(ROOT / "tools/ignored_workspace_verifier.py"),
|
||||
"corpus",
|
||||
"--router",
|
||||
str(binaries["lazaret-ignored-workspace"]),
|
||||
"--git-sentry",
|
||||
str(binaries["lazaret-git-sentry"]),
|
||||
"--work-root",
|
||||
str(root / "work"),
|
||||
"--output",
|
||||
str(root / "result.json"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def verify(process_syscalls: bool) -> None:
|
||||
check_tool_versions()
|
||||
metadata = workspace_metadata()
|
||||
|
|
@ -637,6 +658,7 @@ def verify(process_syscalls: bool) -> None:
|
|||
]
|
||||
)
|
||||
run_rust_tests()
|
||||
run_ignored_workspace_corpus(metadata)
|
||||
run([sys.executable, "-m", "unittest", "discover", "-s", "tests/tooling"])
|
||||
audit_workspace_lints(metadata)
|
||||
audit_dependencies(metadata)
|
||||
|
|
|
|||
Loading…
Reference in a new issue