#![cfg(all(feature = "store", unix))]
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
use yah_mesofact_bundle::{
assemble_vanilla_bundle, ensure_runtime_asset, materialize_bundle, publish_bundle,
publish_runtime_asset, BundleManifest, ContractRequirement, ContractVersion, RuntimeRef,
BUNDLE_CONTRACT_VERSION, IMPLEMENTED_CONTRACTS, SERVE_BIN,
};
use yah_object_store::InMemoryObjectStore;
const TRIPLE: &str = "x86_64-unknown-linux-musl";
const PAGE: &str = "<html><body>yah.dev</body></html>";
struct Row {
version: ContractVersion,
runtime_program: &'static str,
}
const RUNTIME_V1: &str = r#"#!/bin/sh
set -e
[ "$1" = "serve" ] || { echo "contract v1: expected the 'serve' subcommand, got '$1'" >&2; exit 64; }
[ "$2" = "--bundle" ] || { echo "contract v1: expected --bundle, got '$2'" >&2; exit 64; }
bundle="$3"
[ "$4" = "--listen" ] || { echo "contract v1: expected --listen, got '$4'" >&2; exit 64; }
[ -n "$5" ] || { echo "contract v1: --listen takes an address" >&2; exit 64; }
[ -f "$bundle/app/mesofact.routes.ts" ] || { echo "contract v1: no app/mesofact.routes.ts" >&2; exit 65; }
cat "$bundle/app/dist/index.html"
"#;
const MATRIX: &[Row] = &[Row {
version: 1,
runtime_program: RUNTIME_V1,
}];
#[test]
fn every_known_contract_version_has_a_row() {
let rows: Vec<ContractVersion> = MATRIX.iter().map(|r| r.version).collect();
for known in IMPLEMENTED_CONTRACTS {
assert!(
rows.contains(known),
"contract version {known} is in IMPLEMENTED_CONTRACTS but has no matrix row — \
add one to MATRIX in this file so something actually serves against it"
);
}
assert!(
rows.contains(&BUNDLE_CONTRACT_VERSION),
"the version this build EMITS ({BUNDLE_CONTRACT_VERSION}) has no matrix row"
);
for row in MATRIX {
assert!(
IMPLEMENTED_CONTRACTS.contains(&row.version),
"matrix row {} names a contract version no runtime in this tree implements",
row.version
);
}
}
#[test]
fn each_contract_version_serves_against_a_runtime_that_advertises_it() {
for row in MATRIX {
let store = InMemoryObjectStore::new();
let cache = TempDir::new().unwrap();
let src = TempDir::new().unwrap();
let digest = publish_fixture_bundle(&store, src.path(), row.version);
let runtime = RuntimeRef::parse("mesofact/0.9.0").unwrap();
publish_runtime_asset(
&store,
&runtime,
TRIPLE,
SERVE_BIN,
&[row.version],
&write_program(src.path(), row.runtime_program),
)
.unwrap();
let bundle_dir = materialize_bundle(&store, cache.path(), &digest).unwrap();
let manifest = read_manifest(&bundle_dir);
assert_eq!(manifest.requires_contract, row.version);
let serve_bin = ensure_runtime_asset(
&store,
cache.path(),
&runtime,
TRIPLE,
SERVE_BIN,
ContractRequirement::Version(manifest.requires_contract),
)
.unwrap_or_else(|e| panic!("contract {} must resolve: {e}", row.version));
let out = Command::new(&serve_bin)
.args(["serve", "--bundle"])
.arg(&bundle_dir)
.args(["--listen", "127.0.0.1:0"])
.output()
.unwrap_or_else(|e| panic!("forking the contract-{} runtime: {e}", row.version));
assert!(
out.status.success(),
"contract {} runtime rejected the calling convention: {}",
row.version,
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(
String::from_utf8_lossy(&out.stdout).trim(),
PAGE,
"contract {} runtime did not serve the bundle's page",
row.version
);
}
}
#[test]
fn a_bundle_is_refused_by_every_runtime_that_does_not_advertise_its_version() {
for row in MATRIX {
let mut counterparties: Vec<ContractVersion> = MATRIX
.iter()
.map(|o| o.version)
.filter(|v| *v != row.version)
.collect();
counterparties.push(row.version + 1);
if row.version > 1 {
counterparties.push(row.version - 1);
}
for advertised in counterparties {
let store = InMemoryObjectStore::new();
let cache = TempDir::new().unwrap();
let src = TempDir::new().unwrap();
publish_fixture_bundle(&store, src.path(), row.version);
let runtime = RuntimeRef::parse("mesofact/0.9.0").unwrap();
publish_runtime_asset(
&store,
&runtime,
TRIPLE,
SERVE_BIN,
&[advertised],
&write_program(src.path(), row.runtime_program),
)
.unwrap();
let err = match ensure_runtime_asset(
&store,
cache.path(),
&runtime,
TRIPLE,
SERVE_BIN,
ContractRequirement::Version(row.version),
) {
Ok(path) => panic!(
"a runtime advertising only {advertised} must not resolve a bundle \
requiring {}, but it resolved to {}",
row.version,
path.display()
),
Err(e) => e,
};
let msg = err.to_string();
assert!(
msg.contains(&row.version.to_string()) && msg.contains(&advertised.to_string()),
"a refusal must name both versions, got: {msg}"
);
assert!(
!cache
.path()
.join(runtime.cache_rel_serve(TRIPLE))
.exists(),
"a refused resolve must leave nothing forkable in the cache"
);
}
}
}
fn publish_fixture_bundle(
store: &InMemoryObjectStore,
root: &Path,
version: ContractVersion,
) -> yah_mesofact_bundle::BundleHash {
let project = root.join("project");
let out_dir = project.join("dist");
let bundle = root.join("bundle");
write(&project.join("mesofact.routes.ts"), b"export default {}");
write(&out_dir.join("index.html"), PAGE.as_bytes());
let mut manifest =
assemble_vanilla_bundle(&bundle, "yah-marketing", "0.9.0", &project, &out_dir).unwrap();
if manifest.requires_contract != version {
manifest.requires_contract = version;
fs::write(bundle.join("manifest.toml"), manifest.to_toml_string().unwrap()).unwrap();
}
let report = publish_bundle(store, &bundle).unwrap();
assert_eq!(report.digest, manifest.digest());
report.digest
}
fn read_manifest(bundle_dir: &Path) -> BundleManifest {
BundleManifest::from_toml_str(&fs::read_to_string(bundle_dir.join("manifest.toml")).unwrap())
.unwrap()
}
fn write_program(root: &Path, program: &str) -> PathBuf {
let p = root.join("runtime-serve");
fs::write(&p, program).unwrap();
p
}
fn write(path: &Path, bytes: &[u8]) {
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, bytes).unwrap();
}