about summary refs log tree commit diff
path: root/tvix/derivation
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/derivation')
-rw-r--r--tvix/derivation/Cargo.toml19
-rw-r--r--tvix/derivation/src/lib.rs216
-rw-r--r--tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json23
-rw-r--r--tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json19
-rw-r--r--tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json23
-rw-r--r--tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json19
-rw-r--r--tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json16
-rw-r--r--tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json23
-rw-r--r--tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json23
-rw-r--r--tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json23
-rw-r--r--tvix/derivation/src/tests/mod.rs32
19 files changed, 444 insertions, 0 deletions
diff --git a/tvix/derivation/Cargo.toml b/tvix/derivation/Cargo.toml
new file mode 100644
index 000000000000..99dfbb3a104f
--- /dev/null
+++ b/tvix/derivation/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "derivation"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+glob = "0.3.0"
+serde = { version = "1.0", features = ["derive"] }
+
+[dev-dependencies.test-generator]
+# This fork of test-generator adds support for cargo workspaces, see
+# also https://github.com/frehberg/test-generator/pull/14
+git = "https://github.com/JamesGuthrie/test-generator.git"
+rev = "82e799979980962aec1aa324ec6e0e4cad781f41"
+
+[dev-dependencies]
+serde_json = "1.0"
diff --git a/tvix/derivation/src/lib.rs b/tvix/derivation/src/lib.rs
new file mode 100644
index 000000000000..a0ddee410b80
--- /dev/null
+++ b/tvix/derivation/src/lib.rs
@@ -0,0 +1,216 @@
+use serde::{Deserialize, Serialize};
+use std::{collections::BTreeMap, fmt, fmt::Write};
+
+#[cfg(test)]
+mod tests;
+
+const DERIVATION_PREFIX: &str = "Derive";
+const PAREN_OPEN: char = '(';
+const PAREN_CLOSE: char = ')';
+const BRACKET_OPEN: char = '[';
+const BRACKET_CLOSE: char = ']';
+const COMMA: char = ',';
+const QUOTE: char = '"';
+
+const STRING_ESCAPER: [(char, &str); 5] = [
+    ('\\', "\\\\"),
+    ('\n', "\\n"),
+    ('\r', "\\r"),
+    ('\t', "\\t"),
+    ('\"', "\\\""),
+];
+
+fn default_resource() -> String {
+    "".to_string()
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Output {
+    path: String,
+    #[serde(default = "default_resource")]
+    hash_algorithm: String,
+    #[serde(default = "default_resource")]
+    hash: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Derivation {
+    outputs: BTreeMap<String, Output>,
+    input_sources: Vec<String>,
+    input_derivations: BTreeMap<String, Vec<String>>,
+    platform: String,
+    builder: String,
+    arguments: Vec<String>,
+    environment: BTreeMap<String, String>,
+}
+
+fn escape_string(s: &String) -> String {
+    let mut s_replaced = s.clone();
+
+    for escape_sequence in STRING_ESCAPER {
+        s_replaced = s_replaced.replace(escape_sequence.0, escape_sequence.1);
+    }
+
+    return format!("\"{}\"", s_replaced);
+}
+
+fn write_array_elements(
+    writer: &mut impl Write,
+    quote: bool,
+    open: &str,
+    closing: &str,
+    elements: Vec<&String>,
+) -> Result<(), fmt::Error> {
+    writer.write_str(open)?;
+
+    for (index, element) in elements.iter().enumerate() {
+        if index > 0 {
+            writer.write_char(COMMA)?;
+        }
+
+        if quote {
+            writer.write_char(QUOTE)?;
+        }
+
+        writer.write_str(element)?;
+
+        if quote {
+            writer.write_char(QUOTE)?;
+        }
+    }
+
+    writer.write_str(closing)?;
+
+    return Ok(());
+}
+
+pub fn serialize_derivation(
+    derivation: Derivation,
+    writer: &mut impl Write,
+) -> Result<(), fmt::Error> {
+    writer.write_str(DERIVATION_PREFIX)?;
+    writer.write_char(PAREN_OPEN)?;
+
+    // Step 1: Write outputs
+    {
+        writer.write_char(BRACKET_OPEN)?;
+        for (ii, (output_name, output)) in derivation.outputs.iter().enumerate() {
+            if ii > 0 {
+                writer.write_char(COMMA)?;
+            }
+
+            // TODO(jrhahn) option to strip output
+            let elements = vec![
+                output_name,
+                &output.path,
+                &output.hash_algorithm,
+                &output.hash,
+            ];
+
+            write_array_elements(
+                writer,
+                true,
+                &PAREN_OPEN.to_string(),
+                &PAREN_CLOSE.to_string(),
+                elements,
+            )?
+        }
+        writer.write_char(BRACKET_CLOSE)?;
+    }
+
+    // Step 2: Write input_derivations
+    {
+        writer.write_char(COMMA)?;
+        writer.write_char(BRACKET_OPEN)?;
+
+        for (ii, (input_derivation_path, input_derivation)) in
+            derivation.input_derivations.iter().enumerate()
+        {
+            if ii > 0 {
+                writer.write_char(COMMA)?;
+            }
+
+            writer.write_char(PAREN_OPEN)?;
+            writer.write_char(QUOTE)?;
+            writer.write_str(input_derivation_path.as_str())?;
+            writer.write_char(QUOTE)?;
+            writer.write_char(COMMA)?;
+
+            write_array_elements(
+                writer,
+                true,
+                &BRACKET_OPEN.to_string(),
+                &BRACKET_CLOSE.to_string(),
+                input_derivation.iter().map(|s| s).collect(),
+            )?;
+
+            writer.write_char(PAREN_CLOSE)?;
+        }
+
+        writer.write_char(BRACKET_CLOSE)?;
+    }
+
+    // Step 3: Write input_sources
+    {
+        writer.write_char(COMMA)?;
+        write_array_elements(
+            writer,
+            true,
+            &BRACKET_OPEN.to_string(),
+            &BRACKET_CLOSE.to_string(),
+            derivation.input_sources.iter().map(|s| s).collect(),
+        )?;
+    }
+
+    // Step 4: Write platform
+    {
+        writer.write_char(COMMA)?;
+        writer.write_str(&escape_string(&derivation.platform).as_str())?;
+    }
+
+    // Step 5: Write builder
+    {
+        writer.write_char(COMMA)?;
+        writer.write_str(&escape_string(&derivation.builder).as_str())?;
+    }
+
+    // Step 6: Write arguments
+    {
+        writer.write_char(COMMA)?;
+        write_array_elements(
+            writer,
+            true,
+            &BRACKET_OPEN.to_string(),
+            &BRACKET_CLOSE.to_string(),
+            derivation.arguments.iter().map(|s| s).collect(),
+        )?;
+    }
+
+    // Step 7: Write env
+    {
+        writer.write_char(COMMA)?;
+        writer.write_char(BRACKET_OPEN)?;
+
+        for (ii, (key, environment)) in derivation.environment.iter().enumerate() {
+            if ii > 0 {
+                writer.write_char(COMMA)?;
+            }
+
+            // TODO(jrhahn) add strip option
+            write_array_elements(
+                writer,
+                false,
+                &PAREN_OPEN.to_string(),
+                &PAREN_CLOSE.to_string(),
+                vec![&escape_string(key), &escape_string(&environment)],
+            )?;
+        }
+
+        writer.write_char(BRACKET_CLOSE)?;
+    }
+
+    // Step 8: Close Derive call
+    writer.write_char(PAREN_CLOSE)?;
+
+    return Ok(());
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv b/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv
new file mode 100644
index 000000000000..a4fea3c5f486
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar","r:sha256","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")],[],[],":",":",[],[("builder",":"),("name","bar"),("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),("outputHash","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),("outputHashAlgo","sha256"),("outputHashMode","recursive"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json b/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json
new file mode 100644
index 000000000000..abaaa8d4a6fc
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json
@@ -0,0 +1,23 @@
+ {
+  "outputs": {
+    "out": {
+      "path": "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar",
+      "hash_algorithm": "r:sha256",
+      "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "builder": ":",
+    "name": "bar",
+    "out": "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar",
+    "outputHash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+    "outputHashAlgo": "sha256",
+    "outputHashMode": "recursive",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv b/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv
new file mode 100644
index 000000000000..f0d9230a5a52
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json","","")],[],[],":",":",[],[("builder",":"),("json","{\"hello\":\"moto\\n\"}"),("name","nested-json"),("out","/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json b/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json
new file mode 100644
index 000000000000..a1b763ec9449
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json
@@ -0,0 +1,19 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "builder": ":",
+    "json": "{\"hello\":\"moto\\n\"}",
+    "name": "nested-json",
+    "out": "/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv b/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv
new file mode 100644
index 000000000000..a2cf9d31f92e
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo","","")],[("/nix/store/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv",["out"])],[],":",":",[],[("bar","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),("builder",":"),("name","foo"),("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json b/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json
new file mode 100644
index 000000000000..f6bb5be234d1
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json
@@ -0,0 +1,23 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {
+    "/nix/store/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv": [
+      "out"
+    ]
+  },
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "bar": "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar",
+    "builder": ":",
+    "name": "foo",
+    "out": "/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv b/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv
new file mode 100644
index 000000000000..bbe88c02c739
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode","","")],[],[],":",":",[],[("builder",":"),("letters","räksmörgås\nrødgrød med fløde\nLübeck\n肥猪\nこんにちは / 今日は\n🌮\n"),("name","unicode"),("out","/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json b/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json
new file mode 100644
index 000000000000..bf837b3e8665
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json
@@ -0,0 +1,19 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "builder": ":",
+    "letters": "räksmörgås\nrødgrød med fløde\nLübeck\n肥猪\nこんにちは / 今日は\n🌮\n",
+    "name": "unicode",
+    "out": "/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv b/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv
new file mode 100644
index 000000000000..4b9338c0b953
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs","","")],[],[],":",":",[],[("__json","{\"builder\":\":\",\"name\":\"structured-attrs\",\"system\":\":\"}"),("out","/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json b/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json
new file mode 100644
index 000000000000..97f99acdb140
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json
@@ -0,0 +1,16 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "__json": "{\"builder\":\":\",\"name\":\"structured-attrs\",\"system\":\":\"}",
+    "out": "/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv b/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv
new file mode 100644
index 000000000000..1699c2a75e48
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo","","")],[("/nix/store/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv",["out"])],[],":",":",[],[("bar","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),("builder",":"),("name","foo"),("out","/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json b/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json
new file mode 100644
index 000000000000..2d7dbeb75299
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json
@@ -0,0 +1,23 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {
+    "/nix/store/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv": [
+      "out"
+    ]
+  },
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "bar": "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar",
+    "builder": ":",
+    "name": "foo",
+    "out": "/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv b/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv
new file mode 100644
index 000000000000..523612238c76
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv
@@ -0,0 +1 @@
+Derive([("lib","/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib","",""),("out","/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out","","")],[],[],":",":",[],[("builder",":"),("lib","/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib"),("name","has-multi-out"),("out","/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out"),("outputs","out lib"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json b/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json
new file mode 100644
index 000000000000..12cb3481980e
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json
@@ -0,0 +1,23 @@
+{
+  "outputs": {
+    "lib": {
+      "path": "/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib"
+    },
+    "out": {
+      "path": "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "builder": ":",
+    "lib": "/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib",
+    "name": "has-multi-out",
+    "out": "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out",
+    "outputs": "out lib",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv b/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv
new file mode 100644
index 000000000000..559e93ed0ed6
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar","r:sha1","0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33")],[],[],":",":",[],[("builder",":"),("name","bar"),("out","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),("outputHash","0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"),("outputHashAlgo","sha1"),("outputHashMode","recursive"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json b/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json
new file mode 100644
index 000000000000..e159a5d12eed
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json
@@ -0,0 +1,23 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar",
+      "hash_algorithm": "r:sha1",
+      "hash": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "builder": ":",
+    "name": "bar",
+    "out": "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar",
+    "outputHash": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
+    "outputHashAlgo": "sha1",
+    "outputHashMode": "recursive",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/mod.rs b/tvix/derivation/src/tests/mod.rs
new file mode 100644
index 000000000000..9c8ffd061c11
--- /dev/null
+++ b/tvix/derivation/src/tests/mod.rs
@@ -0,0 +1,32 @@
+use super::{serialize_derivation, Derivation};
+use std::fs::File;
+use std::io::Read;
+use std::path::Path;
+use test_generator::test_resources;
+
+fn read_file(path: &str) -> String {
+    let path = Path::new(path);
+    let mut file = File::open(path).unwrap();
+    let mut data = String::new();
+
+    file.read_to_string(&mut data).unwrap();
+
+    return data;
+}
+
+fn assert_derivation_ok(path_to_drv_file: &str) {
+    let data = read_file(&format!("{}.json", path_to_drv_file));
+    let derivation: Derivation = serde_json::from_str(&data).expect("JSON was not well-formatted");
+
+    let mut serialized_derivation = String::new();
+    serialize_derivation(derivation, &mut serialized_derivation).unwrap();
+
+    let expected = read_file(path_to_drv_file);
+
+    assert_eq!(expected, serialized_derivation);
+}
+
+#[test_resources("src/tests/derivation_tests/*.drv")]
+fn derivation_files_ok(path: &str) {
+    assert_derivation_ok(path);
+}