Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,163 @@
|
||||
use super::{Fixture, tools};
|
||||
use dever::compiler::project::dependency::{
|
||||
Lock,
|
||||
library::{command, npm},
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::{collections::BTreeMap, fs, os::unix::fs::DirBuilderExt};
|
||||
|
||||
#[test]
|
||||
fn npm_signed_hooks_native_addon_and_missing_output_replay_are_real() {
|
||||
let mut fixture = Fixture::new();
|
||||
tools::prepare(&mut fixture);
|
||||
let script=br#"const fs=require('node:fs');const path=require('node:path');const child=require('node:child_process');
|
||||
const tools=process.env.PATH.split(':').find(p=>p.endsWith('/rootfs/usr/bin'));
|
||||
if(!tools)throw Error('signed tools PATH missing');
|
||||
if((fs.statSync('executable.cjs').mode&0o111)===0)throw Error('archive executable bit lost');
|
||||
if((fs.statSync('addon.c').mode&0o111)!==0)throw Error('archive executable bit invented');
|
||||
if(fs.readFileSync(path.join(path.dirname(process.execPath),'..','shared.txt'),'utf8')!=='shared runtime fixture')throw Error('shared runtime path changed');
|
||||
if(child.execFileSync(path.join(tools,'fixture-node'),{encoding:'utf8'})!=='signed script')throw Error('signed script did not execute');
|
||||
fs.mkdirSync('build/Release',{recursive:true});
|
||||
child.execFileSync(path.join(tools,'cc'),['-B'+tools+'/','-fno-use-linker-plugin','-fno-lto','-shared','-fPIC','-nostdlib','addon.c','-o','build/Release/addon.node'],{stdio:'inherit'});
|
||||
fs.writeFileSync('generated.js','module.exports=42;');
|
||||
"#;
|
||||
let source=br#"typedef void* napi_env;typedef void* napi_value;typedef unsigned long size_t;
|
||||
extern int napi_create_string_utf8(napi_env,const char*,size_t,napi_value*);
|
||||
__attribute__((visibility("default"))) napi_value napi_register_module_v1(napi_env env,napi_value exports){napi_value result;napi_create_string_utf8(env,"signed addon",12,&result);return result;}
|
||||
"#;
|
||||
fixture.package(json!({"name":"built","version":"1.0.0","optionalDependencies":{"unavailable":"1.0.0"},"scripts":{"install":"node build.cjs","prepare":"node must-not-run.cjs"}}),BTreeMap::from([("package/build.cjs".into(),script.to_vec()),("package/addon.c".into(),source.to_vec()),("package/executable.cjs".into(),b"#!/usr/bin/env node\n".to_vec())]));
|
||||
fixture.package(json!({"name":"unavailable","version":"1.0.0","scripts":{"install":"node -e 'process.exit(7)'"},"bin":{"fixture-bin":"index.js"}}), BTreeMap::new());
|
||||
let project = fixture.directory.0.join("project");
|
||||
fs::DirBuilder::new().mode(0o700).create(&project).unwrap();
|
||||
fs::create_dir(project.join("config")).unwrap();
|
||||
fs::write(project.join("config/setting.json"), "{\"lib\":[]}").unwrap();
|
||||
command::execute_with(
|
||||
"add",
|
||||
&project,
|
||||
&["npm:built@1.0.0".into()],
|
||||
fixture.target,
|
||||
&fixture,
|
||||
&fixture,
|
||||
Some(&fixture.signed),
|
||||
)
|
||||
.unwrap();
|
||||
let lock_file = fs::read_to_string(project.join("dever.lock")).unwrap();
|
||||
let setting = fs::read(project.join("config/setting.json")).unwrap();
|
||||
fixture.lock = Lock::decode(&lock_file).unwrap();
|
||||
let files = fixture.install().unwrap();
|
||||
assert_eq!(
|
||||
files["node_modules/built/generated.js"],
|
||||
b"module.exports=42;"
|
||||
);
|
||||
assert!(files["node_modules/built/build/Release/addon.node"].starts_with(b"\x7fELF"));
|
||||
assert_eq!(fixture.graph().instances.len(), 1);
|
||||
assert_eq!(
|
||||
fixture.graph().instances[0].edges[0].omission,
|
||||
Some(npm::Omission::BuildFailure)
|
||||
);
|
||||
assert_eq!(fixture.lock.libs.len(), 1);
|
||||
let receipt = &fixture.lock.builds[0];
|
||||
assert_eq!(
|
||||
receipt["native_entries"],
|
||||
json!(["node_modules/built/build/Release/addon.node"])
|
||||
);
|
||||
let output = receipt["output"]["sha256"].as_str().unwrap().to_owned();
|
||||
let original = fixture.cache.borrow_mut().remove(&output).unwrap();
|
||||
let sources = receipt["sources"].as_array().unwrap();
|
||||
assert_eq!(sources.len(), 2);
|
||||
let mut expected_urls = vec![];
|
||||
for source in sources {
|
||||
fixture
|
||||
.cache
|
||||
.borrow_mut()
|
||||
.remove(source["sha256"].as_str().unwrap())
|
||||
.unwrap();
|
||||
expected_urls.push(source["source"]["locator"].as_str().unwrap().to_owned());
|
||||
}
|
||||
let lock_before = fixture.lock.encode().unwrap();
|
||||
let calls = fixture.calls.borrow().len();
|
||||
command::execute_with(
|
||||
"install",
|
||||
&project,
|
||||
&[],
|
||||
fixture.target,
|
||||
&fixture,
|
||||
&fixture,
|
||||
Some(&fixture.signed),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(fixture.cache.borrow()[&output], original);
|
||||
assert_eq!(fixture.lock.encode().unwrap(), lock_before);
|
||||
assert_eq!(
|
||||
fs::read_to_string(project.join("dever.lock")).unwrap(),
|
||||
lock_file
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read(project.join("config/setting.json")).unwrap(),
|
||||
setting
|
||||
);
|
||||
let mut fetched = fixture.calls.borrow()[calls..].to_vec();
|
||||
expected_urls.sort();
|
||||
fetched.sort();
|
||||
assert_eq!(fetched, expected_urls);
|
||||
nondeterministic_replay_is_rejected(&mut fixture);
|
||||
failed_native_build_is_not_published(&mut fixture);
|
||||
}
|
||||
|
||||
fn nondeterministic_replay_is_rejected(fixture: &mut Fixture) {
|
||||
fixture.lock = Lock::new(vec![]);
|
||||
fixture.package(json!({"name":"unstable","version":"1.0.0","scripts":{"install":"node -e \"require('node:fs').writeFileSync('nonce',require('node:crypto').randomBytes(16))\""}}), BTreeMap::new());
|
||||
fixture.resolve(&["npm:unstable@1.0.0"]).unwrap();
|
||||
let mut lock = fixture.lock.clone();
|
||||
fixture.reads.borrow_mut().clear();
|
||||
npm::prepare(&mut lock, fixture.target, &fixture.signed, fixture).unwrap();
|
||||
for lib in &lock.libs {
|
||||
let artifact = &lib.artifacts[0];
|
||||
let reads = fixture.reads.borrow();
|
||||
let source_reads = reads
|
||||
.iter()
|
||||
.filter(|(hash, _)| hash == &artifact.sha256)
|
||||
.collect::<Vec<_>>();
|
||||
eprintln!(
|
||||
"npm build: fixture={} bytes={} reads={}",
|
||||
artifact.sha256,
|
||||
artifact.bytes,
|
||||
source_reads.len()
|
||||
);
|
||||
assert_eq!(source_reads.len(), 1);
|
||||
assert_eq!(source_reads[0].1 as u64, artifact.bytes);
|
||||
}
|
||||
fixture.lock = lock;
|
||||
let digest = fixture.lock.builds[0]["output"]["sha256"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
fixture.cache.borrow_mut().remove(&digest).unwrap();
|
||||
let before = fixture.lock.encode().unwrap();
|
||||
let cache_entries = fixture.cache.borrow().len();
|
||||
let error = npm::restore(
|
||||
&fixture.lock,
|
||||
&fixture.graph(),
|
||||
fixture.target,
|
||||
&fixture.signed,
|
||||
fixture,
|
||||
fixture,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(error.contains("not byte reproducible"), "{error}");
|
||||
assert_eq!(fixture.cache.borrow().len(), cache_entries);
|
||||
assert_eq!(fixture.lock.encode().unwrap(), before);
|
||||
}
|
||||
|
||||
fn failed_native_build_is_not_published(fixture: &mut Fixture) {
|
||||
fixture.lock = Lock::new(vec![]);
|
||||
fixture.package(json!({"name":"broken","version":"1.0.0","scripts":{"install":"node -e \"const fs=require('node:fs');fs.mkdirSync('build/Release',{recursive:true});fs.writeFileSync('build/Release/broken.node','invalid addon');\""}}), BTreeMap::new());
|
||||
fixture.resolve(&["npm:broken@1.0.0"]).unwrap();
|
||||
let mut lock = fixture.lock.clone();
|
||||
let before = lock.encode().unwrap();
|
||||
let cache_entries = fixture.cache.borrow().len();
|
||||
let error = npm::prepare(&mut lock, fixture.target, &fixture.signed, fixture).unwrap_err();
|
||||
assert!(error.contains("Lib build failed"), "{error}");
|
||||
assert_eq!(lock.encode().unwrap(), before);
|
||||
assert_eq!(fixture.cache.borrow().len(), cache_entries);
|
||||
}
|
||||
Reference in new issue
Block a user