Files
core/test/rust/dependency/go/build.rs
T
2026-11-03 03:32:07 +00:00

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"
);
}
}