221 lines
7.9 KiB
Rust
221 lines
7.9 KiB
Rust
//! 真实签名测试包通过 production prepare 编译与执行,并验证源码移除后的行为。
|
|
|
|
use super::*;
|
|
use crate::compiler::{build, project::Project};
|
|
use std::{fs, path::Path, process::Command};
|
|
#[path = "build_fixture.rs"]
|
|
mod fixture;
|
|
|
|
struct EmptyStore;
|
|
impl Store for EmptyStore {
|
|
fn get(&self, _: &str, _: u64) -> Result<Option<Vec<u8>>, String> {
|
|
Ok(None)
|
|
}
|
|
fn put(&self, _: &[u8]) -> Result<(), String> {
|
|
panic!("Worker prepare must not write cache")
|
|
}
|
|
}
|
|
|
|
fn project(root: &Path) {
|
|
for (path, source) in [
|
|
("app", "value() (value: Int) { value = port.native() }\n"),
|
|
("port", "native() (value: Int) fails dever.api.Error\n"),
|
|
("adapter", "external go \"worker/main.go\" {}\n"),
|
|
("api", "cmd value = app.value\n"),
|
|
] {
|
|
fixture::write(root, &format!("module/demo/value/{path}.dever"), source);
|
|
}
|
|
fixture::write(
|
|
root,
|
|
"module/demo/value/worker/main.go",
|
|
"package main\nimport (\"context\"; _ \"embed\"; \"dever-worker/helper\")\n//go:embed note.txt\nvar note string\nfunc Native(_ context.Context, _ any, _ any) (any, error) { return map[string]any{\"value\":helper.Value()+Target()+int64(len(note))}, nil }\n",
|
|
);
|
|
fixture::write(
|
|
root,
|
|
"module/demo/value/worker/helper/value.go",
|
|
"package helper\nimport \"dever-worker/helper/internal/value\"\nfunc Value() int64 { return value.Get() }\n",
|
|
);
|
|
fixture::write(
|
|
root,
|
|
"module/demo/value/worker/helper/internal/value/value.go",
|
|
"package value\nfunc Get() int64 { return 40 }\n",
|
|
);
|
|
fixture::write(
|
|
root,
|
|
"module/demo/value/worker/target_linux.go",
|
|
"//go:build linux\npackage main\nfunc Target() int64 { return 1 }\n",
|
|
);
|
|
fixture::write(
|
|
root,
|
|
"module/demo/value/worker/target_windows.go",
|
|
"//go:build windows\npackage main\nfunc Target() int64 { return 99 }\n",
|
|
);
|
|
fixture::write(root, "module/demo/value/worker/note.txt", "x");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "explicit dev-Pod Go authoring fixture; no product tool fallback"]
|
|
fn signed_go_pack_compiles_reproducibly_and_runs_native_after_source_removal() {
|
|
let fixture = fixture::Fixture::new();
|
|
let root = fixture.root.join("project");
|
|
project(&root);
|
|
let project = Project::load(&root).unwrap();
|
|
let program = project.check().unwrap();
|
|
let external = program.external_workers().next().unwrap();
|
|
let target = BuildTarget::host().unwrap();
|
|
let metadata = library::runtime::metadata(&fixture.signed, "go", target).unwrap();
|
|
let mut worker = Worker::from_external(external).unwrap();
|
|
worker.runtime = Some(metadata.pack.clone());
|
|
let mut lock = Lock::new(vec![]);
|
|
lock.workers.push(worker);
|
|
fixture::write(&root, "dever.lock", lock.encode().unwrap());
|
|
let prepare = || {
|
|
crate::compiler::project::dependency::worker::prepare_with_store(
|
|
&root,
|
|
&program,
|
|
target,
|
|
&fixture.signed,
|
|
&EmptyStore,
|
|
)
|
|
};
|
|
let files = prepare().unwrap();
|
|
let repeated = prepare().unwrap();
|
|
assert_eq!(
|
|
files
|
|
.iter()
|
|
.map(|file| (&file.path, &file.bytes, file.executable))
|
|
.collect::<Vec<_>>(),
|
|
repeated
|
|
.iter()
|
|
.map(|file| (&file.path, &file.bytes, file.executable))
|
|
.collect::<Vec<_>>()
|
|
);
|
|
assert!(!files.iter().any(|file| file.path.contains("/source/")
|
|
|| file.path.contains("/stdlib/")
|
|
|| file.path.ends_with("component.go")));
|
|
let binary = build::build_with_resources(
|
|
&program,
|
|
&project.sources,
|
|
"main",
|
|
&root.join("app"),
|
|
target,
|
|
&files,
|
|
)
|
|
.unwrap();
|
|
// 工具与 SDK 篡改、闭包缺失和目标被排除都必须在交付 Worker 前失败。
|
|
let installation = fixture.root.join("installation");
|
|
let release = installation.join("versions/0.0.1");
|
|
let signature = release.join("manifest.sig");
|
|
let original = fs::read(&signature).unwrap();
|
|
fs::write(&signature, "00".repeat(64)).unwrap();
|
|
assert!(SignedResources::load(&installation, "0.0.1").is_err());
|
|
fs::write(&signature, original).unwrap();
|
|
for relative in [
|
|
"sdk/go/component.go".into(),
|
|
format!("runtime/go/{}/runtime.pack", target.name()),
|
|
format!("runtime/go/{}/manifest.json", target.name()),
|
|
] {
|
|
let path = release.join(relative);
|
|
let original = fs::read(&path).unwrap();
|
|
let mut changed = original.clone();
|
|
let middle = changed.len() / 2;
|
|
changed[middle] ^= 1;
|
|
fs::write(&path, changed).unwrap();
|
|
assert!(
|
|
prepare()
|
|
.err()
|
|
.expect("tampered signed asset was accepted")
|
|
.contains("signed resource bytes differ")
|
|
);
|
|
fs::write(&path, original).unwrap();
|
|
let saved = fixture.root.join("missing-asset");
|
|
fs::rename(&path, &saved).unwrap();
|
|
assert!(prepare().is_err(), "missing asset used a host fallback");
|
|
fs::rename(saved, path).unwrap();
|
|
}
|
|
let main = root.join("module/demo/value/worker/main.go");
|
|
let source = fs::read_to_string(&main).unwrap();
|
|
fs::write(
|
|
&main,
|
|
source.replace("dever-worker/helper", "example.com/unlocked"),
|
|
)
|
|
.unwrap();
|
|
assert!(
|
|
prepare()
|
|
.err()
|
|
.expect("unlocked import was accepted")
|
|
.contains("locked module graph")
|
|
);
|
|
fs::write(&main, format!("//go:build windows\n{source}")).unwrap();
|
|
assert!(
|
|
prepare()
|
|
.err()
|
|
.expect("excluded entry was accepted")
|
|
.contains("excluded by target")
|
|
);
|
|
fs::write(&main, &source).unwrap();
|
|
for import in ["internal/abi", "dever-worker/helper/internal/value"] {
|
|
fs::write(
|
|
&main,
|
|
source.replace("_ \"embed\"", &format!("_ \"embed\"; _ {import:?}")),
|
|
)
|
|
.unwrap();
|
|
assert!(
|
|
prepare()
|
|
.err()
|
|
.expect("private import was accepted")
|
|
.contains("Go internal import")
|
|
);
|
|
}
|
|
fs::write(&main, &source).unwrap();
|
|
fixture::write(
|
|
&root,
|
|
"module/demo/value/worker/cgo.go",
|
|
"package main\nimport \"C\"\n",
|
|
);
|
|
assert!(prepare().is_err());
|
|
fs::remove_file(root.join("module/demo/value/worker/cgo.go")).unwrap();
|
|
let descriptor: Descriptor = serde_json::from_value(fixture.descriptor.clone()).unwrap();
|
|
let mut missing = fixture.files.clone();
|
|
missing.remove("stdlib/runtime.a");
|
|
assert!(descriptor.validate(&missing, target).is_err());
|
|
let mut missing = fixture.files.clone();
|
|
missing.remove("bin/compile");
|
|
assert!(descriptor.validate(&missing, target).is_err());
|
|
let mut version = descriptor.clone();
|
|
let actual = version.go_version.split_whitespace().nth(2).unwrap();
|
|
version.go_version = version.go_version.replace(actual, "go0.0.0");
|
|
assert!(
|
|
version
|
|
.validate(&fixture.files, target)
|
|
.unwrap_err()
|
|
.contains("archive target/compiler version differs")
|
|
);
|
|
let mut wrong = fixture.descriptor.clone();
|
|
wrong["goarch"] = "unsupported".into();
|
|
assert!(
|
|
serde_json::from_value::<Descriptor>(wrong)
|
|
.unwrap()
|
|
.validate(&fixture.files, target)
|
|
.is_err()
|
|
);
|
|
fs::remove_dir_all(root.join("module")).unwrap();
|
|
fs::remove_dir_all(fixture.root.join("installation")).unwrap();
|
|
for _ in 0..2 {
|
|
let output = Command::new(&binary)
|
|
.env_clear()
|
|
.args(["demo.value.value", "{}"])
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
output.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
assert_eq!(
|
|
output.stdout,
|
|
b"{\"code\":0,\"message\":\"ok\",\"data\":42}\n"
|
|
);
|
|
}
|
|
}
|