feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the config and the pool both read them), :Plan, :Artifact, :Inventory, :Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL half of :PgWire. Units, jobs, results, the config and the claude stream stay JSON from end to end, so every key keeps its place; :Text gives JSON values truthiness, get, ==, membership and Str(), and throws Dyn::Error under a name for each kind of unexpected data. Exactness where the bytes are seen: - the stream classifiers are hand-written scanners with case-insensitive folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at \n; the SQL call detection takes `<exe> sql` with any path before it; - config problems carry the JSON scanner's and the UTF-8 decoder's own texts, positions included; - a float total is Neumaier's compensated sum, and the build passes -ffp-contract=off: -march=native would fuse a*b+c; - `crafter-review inventory` and `config` print what the goldens record, byte for byte (the fixture and 14 configs, broken ones included); a message names this binary as the command to run. tests/Goldens: the recorded answers over the fixtures next to them (the commits fixture, a rich versioned series with CRLF, a folded subject, Markdown headings in and out of fences, the code-key rewrites); the suite compares 2304 results as DumpFile writes them, so a key out of order fails too. All agree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b02de3ceff
commit
741e3a656d
70 changed files with 30565 additions and 21 deletions
|
|
@ -0,0 +1,30 @@
|
|||
From 1111111111111111111111111111111111111111 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:00:00 +0200
|
||||
Subject: [PATCH 1/2] add the greeting module
|
||||
|
||||
---
|
||||
hello.py | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
create mode 100644 hello.py
|
||||
|
||||
diff --git a/hello.py b/hello.py
|
||||
new file mode 100644
|
||||
index 0000000..a1b2c3d
|
||||
--- /dev/null
|
||||
+++ b/hello.py
|
||||
@@ -0,0 +1,9 @@
|
||||
+"""Greetings."""
|
||||
+
|
||||
+
|
||||
+def greet():
|
||||
+ return "hello"
|
||||
+
|
||||
+
|
||||
+def farewell():
|
||||
+ return "bye"
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
From 2222222222222222222222222222222222222222 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:05:00 +0200
|
||||
Subject: [PATCH 2/2] greet by name and document it
|
||||
|
||||
The greeting takes a name now and the farewell mirrors it; the README
|
||||
documents both calls. Callers that pass nothing still get the old text.
|
||||
|
||||
The format follows the greeting spec [1].
|
||||
|
||||
[1] https://example.org/greeting-spec
|
||||
---
|
||||
README.md | 4 ++++
|
||||
hello.py | 8 ++++++--
|
||||
2 files changed, 10 insertions(+), 2 deletions(-)
|
||||
create mode 100644 README.md
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
new file mode 100644
|
||||
index 0000000..d4e5f6a
|
||||
--- /dev/null
|
||||
+++ b/README.md
|
||||
@@ -0,0 +1,4 @@
|
||||
+# hello
|
||||
+
|
||||
+Call `greet(name)` or `farewell(name)`; without a name both return the
|
||||
+plain text.
|
||||
diff --git a/hello.py b/hello.py
|
||||
index a1b2c3d..b7c8d9e 100644
|
||||
--- a/hello.py
|
||||
+++ b/hello.py
|
||||
@@ -1,9 +1,13 @@
|
||||
"""Greetings."""
|
||||
|
||||
|
||||
-def greet():
|
||||
- return "hello"
|
||||
+def greet(name=None):
|
||||
+ if name is None:
|
||||
+ return "hello"
|
||||
+ return "hello " + name
|
||||
|
||||
|
||||
@@ -7,3 +11,5 @@ def greet():
|
||||
-def farewell():
|
||||
- return "bye"
|
||||
+def farewell(name=None):
|
||||
+ if name is None:
|
||||
+ return "bye"
|
||||
+ return "bye " + name
|
||||
|
||||
base-commit: 9999999999999999999999999999999999999999
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
From 1111111111111111111111111111111111111111 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:00:00 +0200
|
||||
Subject: [PATCH 1/2] add the greeting module
|
||||
|
||||
---
|
||||
hello.py | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
create mode 100644 hello.py
|
||||
|
||||
diff --git a/hello.py b/hello.py
|
||||
new file mode 100644
|
||||
index 0000000..a1b2c3d
|
||||
--- /dev/null
|
||||
+++ b/hello.py
|
||||
@@ -0,0 +1,9 @@
|
||||
+"""Greetings."""
|
||||
+
|
||||
+
|
||||
+def greet():
|
||||
+ return "hello"
|
||||
+
|
||||
+
|
||||
+def farewell():
|
||||
+ return "bye"
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
From 2222222222222222222222222222222222222222 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:05:00 +0200
|
||||
Subject: [PATCH 2/2] greet by name and document it
|
||||
|
||||
The greeting takes a name now and the farewell mirrors it; the README
|
||||
documents both calls. Callers that pass nothing still get the old text.
|
||||
|
||||
The format follows the greeting spec [1].
|
||||
|
||||
[1] https://example.org/greeting-spec
|
||||
---
|
||||
README.md | 4 ++++
|
||||
hello.py | 8 ++++++--
|
||||
2 files changed, 10 insertions(+), 2 deletions(-)
|
||||
create mode 100644 README.md
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
new file mode 100644
|
||||
index 0000000..d4e5f6a
|
||||
--- /dev/null
|
||||
+++ b/README.md
|
||||
@@ -0,0 +1,4 @@
|
||||
+# hello
|
||||
+
|
||||
+Call `greet(name)` or `farewell(name)`; without a name both return the
|
||||
+plain text.
|
||||
diff --git a/hello.py b/hello.py
|
||||
index a1b2c3d..b7c8d9e 100644
|
||||
--- a/hello.py
|
||||
+++ b/hello.py
|
||||
@@ -1,9 +1,13 @@
|
||||
"""Greetings."""
|
||||
|
||||
|
||||
-def greet():
|
||||
- return "hello"
|
||||
+def greet(name=None):
|
||||
+ if name is None:
|
||||
+ return "hello"
|
||||
+ return "hi " + name
|
||||
|
||||
|
||||
@@ -7,3 +11,5 @@ def greet():
|
||||
-def farewell():
|
||||
- return "bye"
|
||||
+def farewell(name=None):
|
||||
+ if name is None:
|
||||
+ return "bye"
|
||||
+ return "bye " + name
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
From 1111111111111111111111111111111111111111 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:00:00 +0200
|
||||
Subject: [PATCH 1/2] add the greeting module
|
||||
|
||||
---
|
||||
hello.py | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
create mode 100644 hello.py
|
||||
|
||||
diff --git a/hello.py b/hello.py
|
||||
new file mode 100644
|
||||
index 0000000..a1b2c3d
|
||||
--- /dev/null
|
||||
+++ b/hello.py
|
||||
@@ -0,0 +1,9 @@
|
||||
+"""Greetings."""
|
||||
+
|
||||
+
|
||||
+def greet():
|
||||
+ return "hello"
|
||||
+
|
||||
+
|
||||
+def farewell():
|
||||
+ return "bye"
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
From 2222222222222222222222222222222222222222 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:05:00 +0200
|
||||
Subject: [PATCH 2/2] greet by name and document it
|
||||
|
||||
The greeting takes a name now and the farewell mirrors it; the README
|
||||
documents both calls. Callers that pass nothing still get the old text.
|
||||
|
||||
The format follows the greeting spec [1].
|
||||
|
||||
[1] https://example.org/greeting-spec
|
||||
---
|
||||
README.md | 4 ++++
|
||||
hello.py | 8 ++++++--
|
||||
2 files changed, 10 insertions(+), 2 deletions(-)
|
||||
create mode 100644 README.md
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
new file mode 100644
|
||||
index 0000000..d4e5f6a
|
||||
--- /dev/null
|
||||
+++ b/README.md
|
||||
@@ -0,0 +1,4 @@
|
||||
+# hello
|
||||
+
|
||||
+Call `greet(name)` or `farewell(name)`; without a name both return the
|
||||
+plain text.
|
||||
diff --git a/hello.py b/hello.py
|
||||
index a1b2c3d..b7c8d9e 100644
|
||||
--- a/hello.py
|
||||
+++ b/hello.py
|
||||
@@ -1,9 +1,13 @@
|
||||
"""Greetings, by name."""
|
||||
|
||||
|
||||
-def greet():
|
||||
- return "hello"
|
||||
+def greet(name=None):
|
||||
+ if name is None:
|
||||
+ return "hello"
|
||||
+ return "hello " + name
|
||||
|
||||
|
||||
@@ -7,3 +11,5 @@ def greet():
|
||||
-def farewell():
|
||||
- return "bye"
|
||||
+def farewell(name=None):
|
||||
+ if name is None:
|
||||
+ return "bye"
|
||||
+ return "bye " + name
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
From 1111111111111111111111111111111111111111 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:00:00 +0200
|
||||
Subject: [PATCH 1/2] add the greeting module
|
||||
|
||||
---
|
||||
hello.py | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
create mode 100644 hello.py
|
||||
|
||||
diff --git a/hello.py b/hello.py
|
||||
new file mode 100644
|
||||
index 0000000..a1b2c3d
|
||||
--- /dev/null
|
||||
+++ b/hello.py
|
||||
@@ -0,0 +1,9 @@
|
||||
+"""Greetings."""
|
||||
+
|
||||
+
|
||||
+def greet():
|
||||
+ return "hello"
|
||||
+
|
||||
+
|
||||
+def farewell():
|
||||
+ return "bye"
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
From 2222222222222222222222222222222222222222 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:05:00 +0200
|
||||
Subject: [PATCH 2/2] greet by name and document it
|
||||
|
||||
The greeting takes a name now and the farewell mirrors it; the README
|
||||
documents both calls. Callers that pass nothing still get the old text.
|
||||
|
||||
The format follows the greeting spec [1].
|
||||
|
||||
[1] https://example.org/greeting-spec
|
||||
---
|
||||
README.md | 4 ++++
|
||||
hello.py | 8 ++++++--
|
||||
2 files changed, 10 insertions(+), 2 deletions(-)
|
||||
create mode 100644 README.md
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
new file mode 100644
|
||||
index 0000000..d4e5f6a
|
||||
--- /dev/null
|
||||
+++ b/README.md
|
||||
@@ -0,0 +1,4 @@
|
||||
+# hello
|
||||
+
|
||||
+Use `greet(name)` or `farewell(name)`; without a name both return the
|
||||
+plain text.
|
||||
diff --git a/hello.py b/hello.py
|
||||
index a1b2c3d..b7c8d9e 100644
|
||||
--- a/hello.py
|
||||
+++ b/hello.py
|
||||
@@ -1,9 +1,13 @@
|
||||
"""Greetings."""
|
||||
|
||||
|
||||
-def greet():
|
||||
- return "hello"
|
||||
+def greet(name=None):
|
||||
+ if name is None:
|
||||
+ return "hello"
|
||||
+ return "hello " + name
|
||||
|
||||
|
||||
@@ -7,3 +11,5 @@ def greet():
|
||||
-def farewell():
|
||||
- return "bye"
|
||||
+def farewell(name=None):
|
||||
+ if name is None:
|
||||
+ return "bye"
|
||||
+ return "bye " + name
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
From 1111111111111111111111111111111111111111 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:00:00 +0200
|
||||
Subject: [PATCH 1/2] add the greeting module
|
||||
|
||||
---
|
||||
hello.py | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
create mode 100644 hello.py
|
||||
|
||||
diff --git a/hello.py b/hello.py
|
||||
new file mode 100644
|
||||
index 0000000..a1b2c3d
|
||||
--- /dev/null
|
||||
+++ b/hello.py
|
||||
@@ -0,0 +1,9 @@
|
||||
+"""Greetings."""
|
||||
+
|
||||
+
|
||||
+def greet():
|
||||
+ return "hello"
|
||||
+
|
||||
+
|
||||
+def farewell():
|
||||
+ return "bye"
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
From 2222222222222222222222222222222222222222 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:05:00 +0200
|
||||
Subject: [PATCH 2/2] greet by name and document it
|
||||
|
||||
The greeting takes a name now and the farewell mirrors it; the README
|
||||
documents both calls. Callers that pass nothing still get the old text.
|
||||
|
||||
The format follows the greeting spec [1].
|
||||
|
||||
[1] https://example.org/greeting-spec
|
||||
---
|
||||
README.md | 4 ++++
|
||||
hello.py | 8 ++++++--
|
||||
2 files changed, 10 insertions(+), 2 deletions(-)
|
||||
create mode 100644 README.md
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
new file mode 100644
|
||||
index 0000000..d4e5f6a
|
||||
--- /dev/null
|
||||
+++ b/README.md
|
||||
@@ -0,0 +1,4 @@
|
||||
+# hello
|
||||
+
|
||||
+Call `greet(name)` or `farewell(name)`; without a name both return the
|
||||
+plain text.
|
||||
diff --git a/hello.py b/hello.py
|
||||
index a1b2c3d..b7c8d9e 100755
|
||||
--- a/hello.py
|
||||
+++ b/hello.py
|
||||
@@ -1,9 +1,13 @@
|
||||
"""Greetings."""
|
||||
|
||||
|
||||
-def greet():
|
||||
- return "hello"
|
||||
+def greet(name=None):
|
||||
+ if name is None:
|
||||
+ return "hello"
|
||||
+ return "hello " + name
|
||||
|
||||
|
||||
@@ -7,3 +11,5 @@ def greet():
|
||||
-def farewell():
|
||||
- return "bye"
|
||||
+def farewell(name=None):
|
||||
+ if name is None:
|
||||
+ return "bye"
|
||||
+ return "bye " + name
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
From 1111111111111111111111111111111111111111 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:00:00 +0200
|
||||
Subject: [PATCH 1/2] add the greeting module
|
||||
|
||||
---
|
||||
hello.py | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
create mode 100644 hello.py
|
||||
|
||||
diff --git a/hello.py b/hello.py
|
||||
new file mode 100644
|
||||
index 0000000..a1b2c3d
|
||||
--- /dev/null
|
||||
+++ b/hello.py
|
||||
@@ -0,0 +1,9 @@
|
||||
+"""Greetings."""
|
||||
+
|
||||
+
|
||||
+def greet():
|
||||
+ return "hello"
|
||||
+
|
||||
+
|
||||
+def farewell():
|
||||
+ return "bye"
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
From 2222222222222222222222222222222222222222 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:05:00 +0200
|
||||
Subject: [PATCH 2/2] greet by name and document it
|
||||
|
||||
---
|
||||
README.md | 4 ++++
|
||||
hello.py | 8 ++++++--
|
||||
2 files changed, 10 insertions(+), 2 deletions(-)
|
||||
create mode 100644 README.md
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
new file mode 100644
|
||||
index 0000000..d4e5f6a
|
||||
--- /dev/null
|
||||
+++ b/README.md
|
||||
@@ -0,0 +1,4 @@
|
||||
+# hello
|
||||
+
|
||||
+Call `greet(name)` or `farewell(name)`; without a name both return the
|
||||
+plain text.
|
||||
diff --git a/hello.py b/hello.py
|
||||
index a1b2c3d..b7c8d9e 100644
|
||||
--- a/hello.py
|
||||
+++ b/hello.py
|
||||
@@ -1,9 +1,13 @@
|
||||
"""Greetings."""
|
||||
|
||||
|
||||
-def greet():
|
||||
- return "hello"
|
||||
+def greet(name=None):
|
||||
+ if name is None:
|
||||
+ return "hello"
|
||||
+ return "hello " + name
|
||||
|
||||
|
||||
@@ -7,3 +11,5 @@ def greet():
|
||||
-def farewell():
|
||||
- return "bye"
|
||||
+def farewell(name=None):
|
||||
+ if name is None:
|
||||
+ return "bye"
|
||||
+ return "bye " + name
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
From 1111111111111111111111111111111111111111 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:00:00 +0200
|
||||
Subject: [PATCH 1/2] add the greeting module
|
||||
|
||||
---
|
||||
hello.py | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
create mode 100644 hello.py
|
||||
|
||||
diff --git a/hello.py b/hello.py
|
||||
new file mode 100644
|
||||
index 0000000..a1b2c3d
|
||||
--- /dev/null
|
||||
+++ b/hello.py
|
||||
@@ -0,0 +1,9 @@
|
||||
+"""Greetings."""
|
||||
+
|
||||
+
|
||||
+def greet():
|
||||
+ return "hello"
|
||||
+
|
||||
+
|
||||
+def farewell():
|
||||
+ return "bye"
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
From 2222222222222222222222222222222222222222 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:05:00 +0200
|
||||
Subject: [PATCH 2/2] greet by name and document it
|
||||
|
||||
The greeting takes a name now and the farewell mirrors it; the README
|
||||
documents both calls. Callers that pass nothing still get the old text.
|
||||
|
||||
The format follows the greeting spec [1].
|
||||
|
||||
[1] https://example.org/greeting-spec
|
||||
---
|
||||
README.md | 4 ++++
|
||||
hello.py | 8 ++++++--
|
||||
2 files changed, 10 insertions(+), 2 deletions(-)
|
||||
create mode 100644 README.md
|
||||
|
||||
diff --git a/hello.py b/hello.py
|
||||
index a1b2c3d..b7c8d9e 100644
|
||||
--- a/hello.py
|
||||
+++ b/hello.py
|
||||
@@ -1,9 +1,13 @@
|
||||
"""Greetings."""
|
||||
|
||||
|
||||
-def greet():
|
||||
- return "hello"
|
||||
+def greet(name=None):
|
||||
+ if name is None:
|
||||
+ return "hello"
|
||||
+ return "hello " + name
|
||||
|
||||
|
||||
@@ -7,3 +11,5 @@ def greet():
|
||||
-def farewell():
|
||||
- return "bye"
|
||||
+def farewell(name=None):
|
||||
+ if name is None:
|
||||
+ return "bye"
|
||||
+ return "bye " + name
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
From bcdef0123456789abcd000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:00:00 +0200
|
||||
Subject: [PATCH 1/3] add the greeting module
|
||||
|
||||
---
|
||||
hello.py | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
create mode 100644 hello.py
|
||||
|
||||
diff --git a/hello.py b/hello.py
|
||||
new file mode 100644
|
||||
index 1234567..89abcde
|
||||
--- /dev/null
|
||||
+++ b/hello.py
|
||||
@@ -2,0 +3,9 @@
|
||||
+"""Greetings."""
|
||||
+
|
||||
+
|
||||
+def greet():
|
||||
+ return "hello"
|
||||
+
|
||||
+
|
||||
+def farewell():
|
||||
+ return "bye"
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.55.0
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
From cdef0123456789abcd0000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:05:00 +0200
|
||||
Subject: [PATCH 2/3] greet by name and document it
|
||||
|
||||
greet() takes a name now and the farewell mirrors it; the README
|
||||
documents both calls. Callers that pass nothing still get the old text.
|
||||
|
||||
The format follows the greeting spec [1].
|
||||
|
||||
[1] https://example.org/greeting-spec
|
||||
---
|
||||
README.md | 4 ++++
|
||||
hello.py | 8 ++++++--
|
||||
2 files changed, 10 insertions(+), 2 deletions(-)
|
||||
create mode 100644 README.md
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
new file mode 100644
|
||||
index 1234567..89abcde
|
||||
--- /dev/null
|
||||
+++ b/README.md
|
||||
@@ -2,0 +3,4 @@
|
||||
+# hello
|
||||
+
|
||||
+Call `greet(name)` or `farewell(name)`; without a name both return the
|
||||
+plain text.
|
||||
diff --git a/hello.py b/hello.py
|
||||
index 1234567..89abcde 100644
|
||||
--- a/hello.py
|
||||
+++ b/hello.py
|
||||
@@ -3,9 +3,13 @@
|
||||
"""Greetings."""
|
||||
|
||||
|
||||
-def greet():
|
||||
- return "hello"
|
||||
+def greet(name=None):
|
||||
+ if name is None:
|
||||
+ return "hello"
|
||||
+ return "hello " + name
|
||||
|
||||
|
||||
@@ -9,3 +13,5 @@ def greet():
|
||||
-def farewell():
|
||||
- return "bye"
|
||||
+def farewell(name=None):
|
||||
+ if name is None:
|
||||
+ return "bye"
|
||||
+ return "bye " + name
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.55.0
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
From 1111111111111111111111111111111111111111 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:00:00 +0200
|
||||
Subject: [PATCH 1/2] add the greeting module
|
||||
|
||||
---
|
||||
hello.py | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
create mode 100644 hello.py
|
||||
|
||||
diff --git a/hello.py b/hello.py
|
||||
new file mode 100644
|
||||
index 0000000..a1b2c3d
|
||||
--- /dev/null
|
||||
+++ b/hello.py
|
||||
@@ -0,0 +1,9 @@
|
||||
+"""Greetings."""
|
||||
+
|
||||
+
|
||||
+def greet():
|
||||
+ return "hello"
|
||||
+
|
||||
+
|
||||
+def farewell():
|
||||
+ return "bye"
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
From 2222222222222222222222222222222222222222 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Wed, 24 Sep 2026 10:05:00 +0200
|
||||
Subject: [PATCH 2/2] greet by name and document it
|
||||
|
||||
The greeting takes a name now and the farewell mirrors it; the README
|
||||
documents both calls. Callers that pass nothing still get the old text.
|
||||
|
||||
The format follows the greeting spec [1].
|
||||
|
||||
[1] https://example.org/greeting-spec
|
||||
---
|
||||
README.md | 4 ++++
|
||||
hello.py | 8 ++++++--
|
||||
2 files changed, 10 insertions(+), 2 deletions(-)
|
||||
create mode 100644 README.md
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
new file mode 100644
|
||||
index 0000000..d4e5f6a
|
||||
--- /dev/null
|
||||
+++ b/README.md
|
||||
@@ -0,0 +1,4 @@
|
||||
+# hello
|
||||
+
|
||||
+Call `greet(name)` or `farewell(name)`; without a name both return the
|
||||
+plain text.
|
||||
diff --git a/hello.py b/hello.py
|
||||
index a1b2c3d..b7c8d9e 100644
|
||||
--- a/hello.py
|
||||
+++ b/hello.py
|
||||
@@ -1,9 +1,13 @@
|
||||
"""Greetings."""
|
||||
|
||||
|
||||
-def greet():
|
||||
- return "hello"
|
||||
+def greet(name=None):
|
||||
+ if name is None:
|
||||
+ return "hello"
|
||||
+ return "hello " + name
|
||||
|
||||
|
||||
@@ -7,3 +11,5 @@ def greet():
|
||||
-def farewell():
|
||||
- return "bye"
|
||||
+def farewell(name=None):
|
||||
+ if name is None:
|
||||
+ return "bye"
|
||||
+ return "bye " + name
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
||||
5
tests/Goldens/fixture/dispositions/0001-a.patch
Normal file
5
tests/Goldens/fixture/dispositions/0001-a.patch
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
x
|
||||
ANCHOR TEXT
|
||||
y
|
||||
ANCHOR TEXT
|
||||
CRLF
|
||||
23519
tests/Goldens/fixture/pipeline.json
Normal file
23519
tests/Goldens/fixture/pipeline.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,60 @@
|
|||
From 1111111111111111111111111111111111111111 Mon Sep 17 00:00:00 2001
|
||||
From: Jörijn Tester <tester@example.com>
|
||||
Date: Wed, 24 Sep 2026 10:00:00 +0200
|
||||
Subject: [PATCH v2 1/3] [RFC] add the docs and a tool, with a subject long enough to
|
||||
be folded onto a second line by git
|
||||
|
||||
The docs explain the tool. A non-breaking space sits in this line,
|
||||
|
||||
and the line before holds nothing else.
|
||||
|
||||
Signed-off-by: Jörijn Tester <tester@example.com>
|
||||
Co-Authored-By: Someone <someone@example.com>
|
||||
---
|
||||
docs/guide.md | 22 ++++++++++++++++++++++
|
||||
tool.py | 4 ++++
|
||||
2 files changed, 26 insertions(+)
|
||||
create mode 100644 docs/guide.md
|
||||
create mode 100755 tool.py
|
||||
|
||||
diff --git a/docs/guide.md b/docs/guide.md
|
||||
new file mode 100644
|
||||
index 0000000..1234567
|
||||
--- /dev/null
|
||||
+++ b/docs/guide.md
|
||||
@@ -0,0 +1,22 @@
|
||||
+# Guide
|
||||
+## Straight after the title
|
||||
+
|
||||
+Intro text.
|
||||
+
|
||||
+## Install
|
||||
+
|
||||
+Run the installer.
|
||||
+
|
||||
+```sh
|
||||
+# not a heading, inside a fence
|
||||
+make install
|
||||
+```
|
||||
+
|
||||
+####### seven hashes are no heading
|
||||
+#nospace is no heading either
|
||||
+ ~~~
|
||||
+## inside a fence indented by three
|
||||
+ ~~~
|
||||
+ ~~~
|
||||
+## a fence indented by four is none
|
||||
+Done.
|
||||
diff --git a/tool.py b/tool.py
|
||||
new file mode 100755
|
||||
index 0000000..89abcde
|
||||
--- /dev/null
|
||||
+++ b/tool.py
|
||||
@@ -0,0 +1,4 @@
|
||||
+#!/usr/bin/env python3
|
||||
+import sys
|
||||
+print("é ünïcödé – a dash, and a line long enough that its excerpt is cut at ninety code points …")
|
||||
+sys.exit(0)
|
||||
--
|
||||
2.55.0
|
||||
|
||||
40
tests/Goldens/fixture/rich/v2-0002-rename-the-tool.patch
Normal file
40
tests/Goldens/fixture/rich/v2-0002-rename-the-tool.patch
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
From 2222222222222222222222222222222222222222 Mon Sep 17 00:00:00 2001
|
||||
From: Jörijn Tester <tester@example.com>
|
||||
Date: Wed, 24 Sep 2026 10:05:00 +0200
|
||||
Subject: [PATCH v2 2/3] rename the tool
|
||||
|
||||
---
|
||||
README.md | 2 --
|
||||
tool.py => bin/tool.py | 4 ++--
|
||||
2 files changed, 2 insertions(+), 4 deletions(-)
|
||||
delete mode 100644 README.md
|
||||
rename tool.py => bin/tool.py (80%)
|
||||
mode change 100755 => 100644
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
deleted file mode 100644
|
||||
index 1111111..0000000
|
||||
--- a/README.md
|
||||
+++ /dev/null
|
||||
@@ -1,2 +0,0 @@
|
||||
-# Old
|
||||
-text
|
||||
diff --git a/tool.py b/bin/tool.py
|
||||
similarity index 80%
|
||||
rename from tool.py
|
||||
rename to bin/tool.py
|
||||
old mode 100755
|
||||
new mode 100644
|
||||
index 89abcde..fedcba9
|
||||
--- a/tool.py
|
||||
+++ b/bin/tool.py
|
||||
@@ -1 +1 @@
|
||||
-#!/usr/bin/env python3
|
||||
+#!/usr/bin/python3
|
||||
@@ -3,2 +3,2 @@ import sys
|
||||
-print("é ünïcödé – a dash")
|
||||
+print("ascii")
|
||||
sys.exit(0)
|
||||
--
|
||||
2.55.0
|
||||
|
||||
26
tests/Goldens/fixture/rich/v2-0003-add-tests.patch
Normal file
26
tests/Goldens/fixture/rich/v2-0003-add-tests.patch
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
From 3333333333333333333333333333333333333333 Mon Sep 17 00:00:00 2001
|
||||
From: Jörijn Tester <tester@example.com>
|
||||
Date: Wed, 24 Sep 2026 10:10:00 +0200
|
||||
Subject: [PATCH v2 3/3] add tests
|
||||
|
||||
Reviewed-by: Someone <someone@example.com>
|
||||
---
|
||||
tests/test_tool.py | 3 +++
|
||||
1 file changed, 3 insertions(+)
|
||||
create mode 100644 tests/test_tool.py
|
||||
|
||||
diff --git a/tests/test_tool.py b/tests/test_tool.py
|
||||
new file mode 100644
|
||||
index 0000000..1111111
|
||||
--- /dev/null
|
||||
+++ b/tests/test_tool.py
|
||||
@@ -0,0 +1,3 @@
|
||||
+def test_tool():
|
||||
+ assert True # x
y
|
||||
+ pass
|
||||
|
||||
base-commit: 0123456789abcdef0123456789abcdef01234567
|
||||
prerequisite-patch-id: abcdef0123456789abcdef0123456789abcdef01
|
||||
--
|
||||
2.55.0
|
||||
|
||||
1
tests/Goldens/fixture/slots/fixture.json
Normal file
1
tests/Goldens/fixture/slots/fixture.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
1
tests/Goldens/fixture/slots/slots/1/slot.json
Normal file
1
tests/Goldens/fixture/slots/slots/1/slot.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"appUrl": "http://localhost:8081/", "apiUrls": ["http://a"], "dbUrl": "postgresql://u:p@127.0.0.1:1/x", "login": "fixture.json"}
|
||||
1
tests/Goldens/fixture/slots/slots/10/slot.json
Normal file
1
tests/Goldens/fixture/slots/slots/10/slot.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"appUrl": "https://h:8443"}
|
||||
1
tests/Goldens/fixture/slots/slots/2/slot.json
Normal file
1
tests/Goldens/fixture/slots/slots/2/slot.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"appUrl": "ftp://x"}
|
||||
1
tests/Goldens/fixture/slots/slots/3/slot.json
Normal file
1
tests/Goldens/fixture/slots/slots/3/slot.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{nope
|
||||
1
tests/Goldens/fixture/slots/slots/4/slot.json
Normal file
1
tests/Goldens/fixture/slots/slots/4/slot.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"appUrl": "http://x", "apiUrls": [1]}
|
||||
1
tests/Goldens/fixture/slots/slots/5/slot.json
Normal file
1
tests/Goldens/fixture/slots/slots/5/slot.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"appUrl": "http://x", "dbUrl": "postgresql://u@db.example.com/x"}
|
||||
1
tests/Goldens/fixture/slots/slots/6/slot.json
Normal file
1
tests/Goldens/fixture/slots/slots/6/slot.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"appUrl": "http://x", "login": "missing.json"}
|
||||
1
tests/Goldens/fixture/slots/slots/7/slot.json
Normal file
1
tests/Goldens/fixture/slots/slots/7/slot.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"appUrl": "http://x", "login": 5}
|
||||
1
tests/Goldens/fixture/slots/slots/x/slot.json
Normal file
1
tests/Goldens/fixture/slots/slots/x/slot.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
[]
|
||||
5
tests/Goldens/fixture/stream-success.jsonl
Normal file
5
tests/Goldens/fixture/stream-success.jsonl
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{"type":"system","subtype":"init","cwd":"/home/jorijn/repos/example","session_id":"11111111-2222-4333-8444-555555555555","tools":["Bash","Glob","Grep","Read","StructuredOutput"],"mcp_servers":[],"model":"claude-opus-5","permissionMode":"default","slash_commands":[],"apiKeySource":"none","claude_code_version":"2.1.263","output_style":"default","agents":["claude","claude-code-guide","Explore","general-purpose","Plan"],"skills":[],"plugins":[],"capabilities":["interrupt_receipt_v1","interrupt_cancel_queued_v1","msg_lifecycle_v1"],"analytics_disabled":true,"product_feedback_disabled":true,"uuid":"685017ca-8568-4652-9e23-830f5da1970d","messaging_socket_path":"/run/user/1000/cc-socks/254300.sock","fast_mode_state":"off","fast_mode_disabled_reason":"unknown"}
|
||||
{"type":"assistant","message":{"model":"claude-opus-5","id":"msg_011Cf7xXggLZUyvd46RvtTQK","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_012DoTWyn2Zsw7bzvkYnA6bq","name":"StructuredOutput","input":{"coverage":[{"item":"R1","verdict":"clear","basis":"fixture"}],"findings":[],"notes":[],"verdict":"SEND-READY"},"caller":{"type":"direct"}}],"container":null,"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":8630,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":8630},"output_tokens":25,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null},"parent_tool_use_id":null,"session_id":"11111111-2222-4333-8444-555555555555","uuid":"27b4e645-8830-4ed3-80e7-3b9b72786291","timestamp":"2026-09-16T23:07:12.378Z","request_id":"req_011Cf7xXgFnbpSD2d9VNWVBQ"}
|
||||
{"type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_012DoTWyn2Zsw7bzvkYnA6bq","type":"tool_result","content":"Structured output provided successfully"}]},"parent_tool_use_id":null,"session_id":"11111111-2222-4333-8444-555555555555","uuid":"5ac21f12-5a67-4ed3-88bd-699742874b2b","timestamp":"2026-09-16T23:07:12.385Z","tool_use_result":"Structured output provided successfully"}
|
||||
{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":1789614000,"rateLimitType":"five_hour","overageStatus":"rejected","overageDisabledReason":"org_level_disabled","isUsingOverage":false,"unifiedWindows":{"five_hour":{"utilization":0.2,"resetsAt":1789614000},"seven_day":{"utilization":0.38,"resetsAt":1789783200}}},"uuid":"56cea5e6-f4ec-4c14-a61d-2d21e1faa9e7","session_id":"11111111-2222-4333-8444-555555555555"}
|
||||
{"duration_api_ms":1916,"stop_reason":"tool_use","session_id":"11111111-2222-4333-8444-555555555555","total_cost_usd":0.090135,"usage":{"input_tokens":2,"cache_creation_input_tokens":8630,"cache_read_input_tokens":0,"output_tokens":153,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":8630,"ephemeral_5m_input_tokens":0},"inference_geo":"not_available","iterations":[],"speed":"standard"},"modelUsage":{"claude-opus-5":{"inputTokens":2,"outputTokens":153,"cacheReadInputTokens":0,"cacheCreationInputTokens":8630,"webSearchRequests":0,"costUSD":0.090135,"contextWindow":1000000,"maxOutputTokens":64000,"thinkingTokens":0,"canonicalModel":"claude-opus-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"unknown","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"is_error":false,"num_turns":2,"subtype":"success","api_error_status":null,"result":"{\"coverage\":[{\"item\":\"R1\",\"verdict\":\"clear\",\"basis\":\"fixture\"}],\"findings\":[],\"notes\":[],\"verdict\":\"SEND-READY\"}","structured_output":{"coverage":[{"item":"R1","verdict":"clear","basis":"fixture"}],"findings":[],"notes":[],"verdict":"SEND-READY"},"ttft_ms":1972,"type":"result","duration_ms":1984,"uuid":"7f3d8d6f-23df-4f6e-8114-ae6caddd353b","ttft_stream_ms":913,"time_to_request_ms":71,"first_content_frame_ms":913,"queued_turn_count":0}
|
||||
431
tests/Goldens/main.cpp
Normal file
431
tests/Goldens/main.cpp
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// The pure pipeline against the goldens: fixture/pipeline.json holds the
|
||||
// recorded answers over the fixtures next to it — the inventory, the plans,
|
||||
// the keys, the prompts, the normalized results, the stream, the config.
|
||||
// Each result is compared as DumpFile writes it, so a key out of place fails
|
||||
// as surely as a wrong value: the dump, the shared files and the prompts are
|
||||
// made of these. A deliberate change edits the goldens in the same commit.
|
||||
|
||||
import std;
|
||||
import Crafter.Review;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter::Review;
|
||||
|
||||
namespace {
|
||||
std::int32_t Failures = 0;
|
||||
std::int32_t Checks = 0;
|
||||
|
||||
void Check(bool cond, std::string_view what) {
|
||||
++Checks;
|
||||
if (!cond) {
|
||||
std::println(std::cerr, "FAIL: {}", what);
|
||||
++Failures;
|
||||
}
|
||||
}
|
||||
|
||||
// Where two texts part, for the failure message.
|
||||
std::string Parting(std::string_view got, std::string_view want) {
|
||||
std::size_t at = 0;
|
||||
while (at < got.size() && at < want.size() && got[at] == want[at]) ++at;
|
||||
auto window = [&](std::string_view text) { return Dyn::Repr(text.substr(at > 40 ? at - 40 : 0, 120)); };
|
||||
return std::format("at byte {}: got {} want {}", at, window(got), window(want));
|
||||
}
|
||||
|
||||
void Same(std::string_view got, std::string_view want, std::string_view what) {
|
||||
Check(got == want, got == want ? std::string(what) : std::format("{}: {}", what, Parting(got, want)));
|
||||
}
|
||||
|
||||
void SameJson(const Json& got, const Json& want, std::string_view what) {
|
||||
Same(DumpFile(got), DumpFile(want), what);
|
||||
}
|
||||
|
||||
std::vector<std::string> Strings(const Json& array) {
|
||||
std::vector<std::string> out;
|
||||
if (array.is_null()) return out;
|
||||
for (const Json& item : array) out.push_back(item.get<std::string>());
|
||||
return out;
|
||||
}
|
||||
|
||||
Json ToJson(const std::vector<std::string>& values) {
|
||||
Json out = Json::array();
|
||||
for (const std::string& value : values) out.push_back(value);
|
||||
return out;
|
||||
}
|
||||
|
||||
Json Optional(const std::optional<std::string>& value) {
|
||||
return value ? Json(*value) : Json();
|
||||
}
|
||||
|
||||
std::optional<std::string> OptionalString(const Json& value) {
|
||||
if (value.is_null()) return std::nullopt;
|
||||
return value.get<std::string>();
|
||||
}
|
||||
|
||||
Json CommitCodeJson(const Plan::CommitCode& cc) {
|
||||
Json body = Json::object();
|
||||
Json code = Json::object();
|
||||
Json docs = Json::object();
|
||||
Json message = Json::object();
|
||||
Json upto = Json::object();
|
||||
for (const auto& [k, v] : cc.body) body[std::to_string(k)] = v;
|
||||
for (const auto& [k, v] : cc.code) code[std::to_string(k)] = v;
|
||||
for (const auto& [k, v] : cc.docs) docs[std::to_string(k)] = ToJson(v);
|
||||
for (const auto& [k, v] : cc.message) message[std::to_string(k)] = v;
|
||||
for (const auto& [k, v] : cc.upto) upto[std::to_string(k)] = v;
|
||||
return {{"body", body}, {"code", code}, {"docs", docs}, {"msg", message}, {"range", cc.range}, {"upto", upto}, {"base", cc.base}};
|
||||
}
|
||||
|
||||
void CheckSeries(const std::string& name, const Json& golden) {
|
||||
const std::string dir = golden.at("dir").get<std::string>();
|
||||
const Inventory::Built built = Inventory::Build("commits", dir);
|
||||
SameJson(built.out, golden.at("inventory"), std::format("{}: inventory", name));
|
||||
SameJson(ToJson(built.lines), golden.at("lines"), std::format("{}: lines", name));
|
||||
Same(Artifact::ArtifactHash("commits", dir), golden.at("hash").get<std::string>(), std::format("{}: artifact hash", name));
|
||||
const Plan::CommitCode cc = Plan::CommitCodeOf(Artifact::PatchTexts(dir));
|
||||
SameJson(CommitCodeJson(cc), golden.at("commitCode"), std::format("{}: commit_code", name));
|
||||
const Json& units = built.out.at("units");
|
||||
for (auto it = golden.at("codeKeyParts").begin(); it != golden.at("codeKeyParts").end(); ++it) {
|
||||
const auto [ident, deps] = Plan::CodeKeyParts(it.key(), cc);
|
||||
SameJson(Json::array({ident, ToJson(deps)}), it.value(), std::format("{}: code_key_parts {}", name, it.key()));
|
||||
}
|
||||
std::map<std::string, Json> blocksByLens;
|
||||
std::map<std::string, Json> wholeByLens;
|
||||
const std::vector<std::string> exclude = {"**/*.test.*", "docs/**", "tests/**"};
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
const Plan::LensPlan plan = Plan::PlanLens(lens.file, units, built.lines, {}, exclude);
|
||||
Json blocks = Json::array();
|
||||
for (const Json& block : plan.blocks) blocks.push_back(block);
|
||||
blocksByLens[std::string(lens.file)] = blocks;
|
||||
wholeByLens[std::string(lens.file)] = plan.whole;
|
||||
}
|
||||
for (auto it = golden.at("keys").begin(); it != golden.at("keys").end(); ++it) {
|
||||
const std::vector<std::string> tag = Dyn::Split(it.key(), "|");
|
||||
const std::string& lensFile = tag[0];
|
||||
const std::string environment = tag[1] == "None" ? std::string() : tag[1];
|
||||
const std::vector<std::string> ids = tag[2].empty() ? std::vector<std::string>() : Dyn::Split(tag[2], ",");
|
||||
Json keys = Json::object();
|
||||
for (const Json& job : blocksByLens[lensFile]) {
|
||||
if (job.at("kind") != "block") continue;
|
||||
const std::string unit = job.at("unit").get<std::string>();
|
||||
const Json* found = nullptr;
|
||||
for (const Json& u : units) {
|
||||
if (u.at("id") == unit) found = &u;
|
||||
}
|
||||
keys[unit] = Optional(Plan::BlockReuseKey(job, lensFile, Plan::UnitText(built.lines, *found), cc, ids, std::string(64, 'c'), 1, environment));
|
||||
}
|
||||
SameJson(keys, it.value(), std::format("{}: block keys {}", name, it.key()));
|
||||
const Json& whole = wholeByLens[lensFile];
|
||||
const Json wholeKey = whole.is_null() ? Json() : Optional(Plan::WholeCodeKey(whole, lensFile, cc, ids, std::string(64, 'c'), 1, environment));
|
||||
SameJson(wholeKey, golden.at("wholeKeys").at(it.key()), std::format("{}: whole key {}", name, it.key()));
|
||||
}
|
||||
if (!golden.contains("plans")) return;
|
||||
Same(Artifact::PatchHead(dir).value_or("None"), golden.at("head").is_null() ? "None" : golden.at("head").get<std::string>(), std::format("{}: patch_head", name));
|
||||
SameJson(ToJson(Artifact::PatchFiles(dir)), golden.at("patchFiles"), std::format("{}: patch_files", name));
|
||||
for (const auto& [k, text] : Artifact::PatchTexts(dir)) {
|
||||
const Plan::PatchParts parts = Plan::PartsOf(text);
|
||||
SameJson(Json::array({parts.hasBody, parts.code, parts.base, ToJson(parts.docs), parts.message}), golden.at("patchParts").at(std::to_string(k)),
|
||||
std::format("{}: patch_parts {}", name, k));
|
||||
}
|
||||
for (std::size_t i = 0; i < units.size(); ++i) {
|
||||
const Json& want = golden.at("units").at(i);
|
||||
const std::string id = units[i].at("id").get<std::string>();
|
||||
const std::string text = Plan::UnitText(built.lines, units[i]);
|
||||
Same(Plan::UnitKind(units[i]), want.at("kind").get<std::string>(), std::format("{}: unit_kind {}", name, id));
|
||||
Check(Plan::IsDocUnit(id) == want.at("doc").get<bool>(), std::format("{}: is_doc_unit {}", name, id));
|
||||
SameJson(Optional(Plan::UnitPath(id)), want.at("path"), std::format("{}: unit_path {}", name, id));
|
||||
Same(text, want.at("text").get<std::string>(), std::format("{}: unit_text {}", name, id));
|
||||
Same(Plan::CodeNorm(text), want.at("norm").get<std::string>(), std::format("{}: code_norm {}", name, id));
|
||||
}
|
||||
std::vector<std::string> all;
|
||||
std::vector<std::string> statics;
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
all.emplace_back(lens.file);
|
||||
if (lens.file != Plan::FunctionalLens) statics.emplace_back(lens.file);
|
||||
}
|
||||
const std::vector<std::string> functional = {std::string(Plan::FunctionalLens)};
|
||||
SameJson(ToJson(Plan::NotReviewed(units, built.lines, all)), golden.at("notReviewed").at("all"), std::format("{}: not_reviewed all", name));
|
||||
SameJson(ToJson(Plan::NotReviewed(units, built.lines, statics)), golden.at("notReviewed").at("static"), std::format("{}: not_reviewed static", name));
|
||||
SameJson(ToJson(Plan::NotReviewed(units, built.lines, functional, exclude)), golden.at("notReviewed").at("functional"), std::format("{}: not_reviewed functional", name));
|
||||
std::vector<Json> jobs;
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
for (const bool excluded : {false, true}) {
|
||||
const Plan::LensPlan plan = Plan::PlanLens(lens.file, units, built.lines, {}, excluded ? std::span<const std::string>(exclude) : std::span<const std::string>());
|
||||
Json blocks = Json::array();
|
||||
for (const Json& block : plan.blocks) blocks.push_back(block);
|
||||
const std::string key = std::format("{}{}", lens.file, excluded ? ":exclude" : "");
|
||||
SameJson(Json{{"whole", plan.whole}, {"blocks", blocks}}, golden.at("plans").at(key), std::format("{}: plan_lens {}", name, key));
|
||||
if (!excluded) {
|
||||
if (!plan.whole.is_null()) jobs.push_back(plan.whole);
|
||||
jobs.insert(jobs.end(), plan.blocks.begin(), plan.blocks.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
SameJson(Plan::Estimate(jobs, 123457, {{"repo-better-solution", 4100}, {"repo-functional", 9000}}), golden.at("estimate"), std::format("{}: estimate", name));
|
||||
}
|
||||
|
||||
void CheckPlan(const Json& g) {
|
||||
for (const Json& entry : g.at("unitIds")) {
|
||||
const std::string id = entry.at("id").get<std::string>();
|
||||
Same(Plan::UnitKindOf(id), entry.at("kind").get<std::string>(), std::format("unit_kind {}", Dyn::Repr(id)));
|
||||
Check(Plan::IsDocUnit(id) == entry.at("doc").get<bool>(), std::format("is_doc_unit {}", Dyn::Repr(id)));
|
||||
SameJson(Optional(Plan::UnitPath(id)), entry.at("path"), std::format("unit_path {}", Dyn::Repr(id)));
|
||||
}
|
||||
for (const Json& entry : g.at("excludedBy")) {
|
||||
const std::vector<std::string> globs = Strings(entry.at(1));
|
||||
SameJson(Optional(Plan::ExcludedBy(entry.at(0).get<std::string>(), globs)), entry.at(2), std::format("excluded_by {}", DumpCompactSorted(entry)));
|
||||
}
|
||||
for (const Json& entry : g.at("gates")) {
|
||||
const Json& args = entry.at(0);
|
||||
const std::vector<std::string> only = args.contains("only") ? Strings(args.at("only")) : std::vector<std::string>();
|
||||
const std::vector<std::string> without = args.contains("without") ? Strings(args.at("without")) : std::vector<std::string>();
|
||||
const std::vector<std::string> off = args.contains("off") ? Strings(args.at("off")) : std::vector<std::string>();
|
||||
Json got = Json::array();
|
||||
for (const Plan::Gate& gate : Plan::GatesFor(only, without, off)) {
|
||||
Json lenses = Json::array();
|
||||
for (const std::string_view lens : gate.lenses) lenses.push_back(lens);
|
||||
got.push_back(Json::array({gate.title, lenses}));
|
||||
}
|
||||
SameJson(got, entry.at(1), std::format("gates_for {}", DumpCompactSorted(args)));
|
||||
}
|
||||
for (const Json& entry : g.at("codeNorm")) {
|
||||
Same(Plan::CodeNorm(entry.at(0).get<std::string>()), entry.at(1).get<std::string>(), std::format("code_norm {}", Dyn::Repr(entry.at(0).get<std::string>())));
|
||||
}
|
||||
SameJson(Optional(Plan::ReuseKey({{"kind", "block"}, {"unit", "U"}}, "repo-bug-prover.md", "T", {"d", "b"}, {"w2", "w1"}, "cs", 1)), g.at("reuseKey"), "reuse_key");
|
||||
for (const Json& entry : g.at("readsOf")) Check(Plan::ReadsOf(entry.at(0)) == entry.at(1).get<std::int64_t>(), std::format("reads_of {}", DumpCompactSorted(entry.at(0))));
|
||||
for (const Json& entry : g.at("readIn")) {
|
||||
Check(Plan::ReadIn(entry.at(0), entry.at(1).get<std::string>(), entry.at(2).get<std::int64_t>()) == entry.at(3).get<bool>(), std::format("read_in {}", DumpCompactSorted(entry)));
|
||||
}
|
||||
SameJson(Plan::Rebind(g.at("rebind").at(0), "p2:hello.py#3"), g.at("rebind").at(1), "rebind");
|
||||
}
|
||||
|
||||
void CheckPrompts(const Json& g, const Json& series) {
|
||||
const Json& p = g.at("prompts");
|
||||
Same(Prompt::SepRun, p.at("separators").at(0).get<std::string>(), "SEP_RUN");
|
||||
Same(Prompt::SepLens, p.at("separators").at(1).get<std::string>(), "SEP_LENS");
|
||||
Same(Prompt::SepTail, p.at("separators").at(2).get<std::string>(), "SEP_TAIL");
|
||||
Same(Prompt::GenericVocab, p.at("genericVocab").get<std::string>(), "GENERIC_VOCAB");
|
||||
Same(Prompt::WholeFirstRepo, p.at("wholeText").at("first-repo").get<std::string>(), "WHOLE_TEXT first-repo");
|
||||
Same(Prompt::WholeFunctional, p.at("wholeText").at("functional").get<std::string>(), "WHOLE_TEXT functional");
|
||||
Same(Prompt::FunctionalBlockHint, p.at("blockHint").get<std::string>(), "FUNCTIONAL_BLOCK_HINT");
|
||||
Same(Prompt::ShotRules, p.at("shotRules").get<std::string>(), "SHOT_RULES");
|
||||
Same(Prompt::HintOutside, p.at("kindHints").at("outside").get<std::string>(), "KIND_HINTS_REPO outside");
|
||||
Same(Prompt::HintAbsent, p.at("kindHints").at("absent").get<std::string>(), "KIND_HINTS_REPO absent");
|
||||
Same(Prompt::SystemText(), p.at("systemText").get<std::string>(), "system_text");
|
||||
for (auto it = p.at("charterSha").begin(); it != p.at("charterSha").end(); ++it) {
|
||||
Same(Plan::Sha(Prompt::Charter(it.key())), it.value().get<std::string>(), std::format("charter sha {}", it.key()));
|
||||
}
|
||||
for (const Json& entry : p.at("runLayer")) {
|
||||
const Json& s = series.at(entry.at("series").get<std::string>());
|
||||
const Json& args = entry.at("args");
|
||||
const std::vector<std::string> trees = Strings(args.at("trees"));
|
||||
const std::vector<std::string> context = Strings(args.at("context_paths"));
|
||||
const std::vector<std::string> lines = Strings(s.at("lines"));
|
||||
Same(Prompt::RunLayer(args.at("artifact_rel").get<std::string>(), args.at("base").get<std::string>(), args.at("date").get<std::string>(), trees,
|
||||
s.at("hash").get<std::string>(), lines, s.at("inventory").at("units"), context, args.at("repo_name").get<std::string>()),
|
||||
entry.at("text").get<std::string>(), std::format("run_layer {} {}", entry.at("series").get<std::string>(), DumpCompactSorted(args)));
|
||||
}
|
||||
for (const Json& entry : p.at("appBlock")) {
|
||||
Same(Prompt::AppBlock(entry.at("functional"), OptionalString(entry.at("fixture")), OptionalString(entry.at("totp")), entry.at("slots").get<bool>()),
|
||||
entry.at("text").get<std::string>(), std::format("app_block {}", DumpCompactSorted(entry.at("functional"))));
|
||||
}
|
||||
for (const Json& entry : p.at("stackText")) {
|
||||
Same(Prompt::StackText(entry.at("stack"), OptionalString(entry.at("totp"))), entry.at("text").get<std::string>(), "stack_text");
|
||||
}
|
||||
for (const Json& entry : p.at("dbBlock")) {
|
||||
const std::optional<bool> bypass = entry.at("bypass").is_null() ? std::nullopt : std::optional<bool>(entry.at("bypass").get<bool>());
|
||||
Same(Prompt::DbBlock(entry.at("supabase"), "/app/bin/crafter-review sql", OptionalString(entry.at("user")), entry.at("withApp").get<bool>(),
|
||||
OptionalString(entry.at("checked")), bypass),
|
||||
entry.at("text").get<std::string>(), std::format("db_block {}", DumpCompactSorted(entry.at("supabase"))));
|
||||
}
|
||||
for (const Json& entry : p.at("lensLayer")) {
|
||||
const std::string lensFile = entry.at("lensFile").get<std::string>();
|
||||
Same(Prompt::LensLayer(lensFile, Prompt::Charter(lensFile), "DISPOSITIONS BLOCK", OptionalString(entry.at("app")).value_or(""),
|
||||
OptionalString(entry.at("summary")).value_or(""), OptionalString(entry.at("db")).value_or("")),
|
||||
entry.at("text").get<std::string>(), std::format("lens_layer {}", lensFile));
|
||||
}
|
||||
for (const Json& entry : p.at("tails")) {
|
||||
const Json& s = series.at(entry.at("series").get<std::string>());
|
||||
const Json& plan = s.at("plans").at(entry.at("plan").get<std::string>());
|
||||
const Json* job = plan.at("whole").is_object() && plan.at("whole").at("label") == entry.at("label") ? &plan.at("whole") : nullptr;
|
||||
for (const Json& block : plan.at("blocks")) {
|
||||
if (block.at("label") == entry.at("label")) job = █
|
||||
}
|
||||
const std::vector<std::string> lines = Strings(s.at("lines"));
|
||||
Same(Prompt::Tail(*job, s.at("inventory").at("units"), lines, OptionalString(entry.at("summary")).value_or("")), entry.at("text").get<std::string>(),
|
||||
std::format("tail {} {}", entry.at("series").get<std::string>(), entry.at("label").get<std::string>()));
|
||||
}
|
||||
for (const Json& entry : p.at("userPrompt")) {
|
||||
Same(Prompt::UserPrompt(entry.at("tail").get<std::string>(), entry.at("job"), OptionalString(entry.at("stack")).value_or("")), entry.at("text").get<std::string>(),
|
||||
std::format("user_prompt {}", DumpCompactSorted(entry.at("job"))));
|
||||
}
|
||||
Same(Prompt::SystemPrompt("S", "R", "L"), p.at("systemPrompt").get<std::string>(), "system_prompt");
|
||||
}
|
||||
|
||||
void CheckDispositions(const Json& g) {
|
||||
const Json& d = g.at("dispositions");
|
||||
const std::vector<Json> entries(d.at("entries").begin(), d.at("entries").end());
|
||||
const std::vector<Json> rows = Dispositions::Classify(entries, "H1", d.at("dir").get<std::string>());
|
||||
SameJson(Json(rows), d.at("rows"), "dispositions classify");
|
||||
const std::vector<Json> inForce = Dispositions::InForce(entries, rows);
|
||||
SameJson(Json(inForce), d.at("inForce"), "dispositions in_force");
|
||||
for (auto it = d.at("blocks").begin(); it != d.at("blocks").end(); ++it) {
|
||||
Same(Dispositions::Block(inForce, rows, it.key(), "H1"), it.value().get<std::string>(), std::format("dispositions block {}", it.key()));
|
||||
}
|
||||
Same(Dispositions::Block({}, {}, Plan::FunctionalLens, "H9"), d.at("empty").get<std::string>(), "dispositions block (none)");
|
||||
const std::vector<Json> two(entries.begin() + 1, entries.begin() + 3);
|
||||
SameJson(Json(Dispositions::Classify(two, "H9", fs::path(d.at("dir").get<std::string>()) / "0001-a.patch")), d.at("rowsFile"), "dispositions classify (a file)");
|
||||
}
|
||||
|
||||
void CheckNormalize(const Json& g) {
|
||||
std::int32_t n = 0;
|
||||
for (const Json& entry : g.at("normalize")) {
|
||||
Json v = entry.at("v");
|
||||
const std::vector<Json> inForce(entry.at("inForce").begin(), entry.at("inForce").end());
|
||||
const std::vector<std::string> allIds = Strings(entry.at("allIds"));
|
||||
const Json result = Normalize::NormalizeOutput(v, inForce, entry.at("lensFile").get<std::string>(), entry.at("hash").get<std::string>(), entry.at("inventory"), allIds);
|
||||
SameJson(result, entry.at("result"), std::format("normalize case {}", n));
|
||||
SameJson(v, entry.at("vAfter"), std::format("normalize case {}: the output's notes after", n));
|
||||
++n;
|
||||
}
|
||||
for (const Json& entry : g.at("resolveId")) {
|
||||
SameJson(Normalize::ResolveId(entry.at(0), Strings(entry.at(1))), entry.at(2), std::format("resolve_id {}", DumpCompactSorted(entry.at(0))));
|
||||
}
|
||||
n = 0;
|
||||
for (const Json& entry : g.at("merge")) {
|
||||
const std::vector<Json> parts(entry.at("parts").begin(), entry.at("parts").end());
|
||||
SameJson(Normalize::MergeParts(entry.at("lens").get<std::string>(), entry.at("lensFile").get<std::string>(), parts, Strings(entry.at("missing")), entry.at("stopped")),
|
||||
entry.at("result"), std::format("merge_parts case {}", n++));
|
||||
}
|
||||
n = 0;
|
||||
for (const Json& entry : g.at("matrix")) {
|
||||
const std::vector<Json> results(entry.at("results").begin(), entry.at("results").end());
|
||||
SameJson(Normalize::Matrix(entry.at("inventory"), results, Strings(entry.at("notReviewed"))), entry.at("result"), std::format("matrix case {}", n++));
|
||||
}
|
||||
for (const Json& entry : g.at("dedupe")) {
|
||||
const std::vector<Json> items(entry.at(0).begin(), entry.at(0).end());
|
||||
SameJson(Json(Normalize::Dedupe(items)), entry.at(1), "dedupe");
|
||||
}
|
||||
}
|
||||
|
||||
void CheckStream(const Json& g) {
|
||||
const Json& s = g.at("stream");
|
||||
std::map<std::string, Json> parsed;
|
||||
for (auto it = s.at("streams").begin(); it != s.at("streams").end(); ++it) {
|
||||
parsed[it.key()] = Stream::ParseStream(it.value().get<std::string>());
|
||||
SameJson(parsed[it.key()], s.at("parsed").at(it.key()), std::format("parse_stream {}", it.key()));
|
||||
const std::optional<Json> structured = Stream::StructuredOutput(parsed[it.key()].at("result"));
|
||||
SameJson(structured ? *structured : Json(), s.at("structured").at(it.key()), std::format("structured_output {}", it.key()));
|
||||
}
|
||||
for (const Json& entry : s.at("classify")) {
|
||||
const std::optional<std::int32_t> code = entry.at(1).is_null() ? std::nullopt : std::optional<std::int32_t>(entry.at(1).get<std::int32_t>());
|
||||
const auto [status, reason] = Stream::Classify(parsed.at(entry.at(0).get<std::string>()), code, entry.at(2).get<std::string>(), entry.at(3).get<bool>());
|
||||
SameJson(Json::array({status, reason}), entry.at(4), std::format("classify {} {}", entry.at(0).get<std::string>(), DumpCompactSorted(entry.at(2))));
|
||||
}
|
||||
for (const Json& entry : s.at("windows")) SameJson(Stream::Windows(entry.at(0)), entry.at(1), std::format("windows {}", DumpCompactSorted(entry.at(0))));
|
||||
for (const Json& entry : s.at("structuredOf")) {
|
||||
const std::optional<Json> structured = Stream::StructuredOutput(entry.at(0));
|
||||
SameJson(structured ? *structured : Json(), entry.at(1), std::format("structured_output {}", DumpCompactSorted(entry.at(0))));
|
||||
}
|
||||
for (const Json& entry : g.at("patterns")) {
|
||||
const std::string text = entry.at(0).get<std::string>();
|
||||
Check(Stream::LimitText(text) == entry.at(1).get<bool>(), std::format("LIMIT_RE {}", Dyn::Repr(text)));
|
||||
Check(Stream::AuthText(text) == entry.at(2).get<bool>(), std::format("AUTH_RE {}", Dyn::Repr(text)));
|
||||
Check(Stream::CreditText(text) == entry.at(3).get<bool>(), std::format("CREDIT_RE {}", Dyn::Repr(text)));
|
||||
Check(Stream::IsSqlCall(text) == entry.at(4).get<bool>(), std::format("SQL_CALL {}", Dyn::Repr(text)));
|
||||
Check(Stream::HasAnsweredLine(text) == entry.at(5).get<bool>(), std::format("ANSWERED_LINE {}", Dyn::Repr(text)));
|
||||
}
|
||||
}
|
||||
|
||||
void CheckState(const Json& g) {
|
||||
const Json& s = g.at("state");
|
||||
std::int32_t n = 0;
|
||||
for (const Json& entry : s.at("merge")) {
|
||||
SameJson(State::MergeState(entry.at("disk"), entry.at("sets"), Strings(entry.at("drops")), entry.at("read")), entry.at("result"), std::format("merge_state case {}", n++));
|
||||
}
|
||||
n = 0;
|
||||
for (const Json& entry : s.at("usage")) {
|
||||
const std::vector<Json> records(entry.at(0).begin(), entry.at(0).end());
|
||||
SameJson(State::UsageTotals(records), entry.at(1), std::format("usage_totals case {}", n++));
|
||||
}
|
||||
for (const Json& entry : s.at("describe")) Same(State::Describe(entry.at(0)), entry.at(1).get<std::string>(), std::format("_describe {}", DumpCompactSorted(entry.at(0))));
|
||||
n = 0;
|
||||
for (const Json& entry : s.at("summary")) Same(State::Summary(entry.at(0)), entry.at(1).get<std::string>(), std::format("summary case {}", n++));
|
||||
}
|
||||
|
||||
std::string Spelled(std::string text, const fs::path& repo) {
|
||||
text = Dyn::Replace(text, repo.string(), "<repo>");
|
||||
return Dyn::Replace(text, std::format("`{} slots`", Invocation()), "`<invocation> slots`");
|
||||
}
|
||||
|
||||
void CheckConfig(const Json& g) {
|
||||
const Json& c = g.at("config");
|
||||
const fs::path scratch = fs::temp_directory_path() / std::format("crafter-review-goldens-{}", Platform::PublishedPid());
|
||||
for (const Json& entry : c.at("cases")) {
|
||||
const std::string name = entry.at("name").get<std::string>();
|
||||
const fs::path repo = scratch / name;
|
||||
fs::remove_all(repo);
|
||||
fs::create_directories(repo);
|
||||
if (!entry.at("body").is_null()) {
|
||||
fs::create_directories(repo / ".review-gates");
|
||||
std::ofstream(repo / ".review-gates" / "config.json", std::ios::binary) << entry.at("body").get<std::string>();
|
||||
}
|
||||
const Config::Loaded loaded = Config::Load(repo);
|
||||
std::vector<std::string> problems;
|
||||
for (const std::string& problem : loaded.problems) problems.push_back(Spelled(problem, repo));
|
||||
SameJson(ToJson(problems), entry.at("problems"), std::format("config {}: problems", name));
|
||||
SameJson(Json::parse(Spelled(DumpFile(Config::Redacted(loaded.config)), repo)), entry.at("config"), std::format("config {}: redacted", name));
|
||||
const Json& path = Dyn::Get(loaded.config, "_path");
|
||||
SameJson(path.is_null() ? Json() : Json(Spelled(path.get<std::string>(), repo)), entry.at("path"), std::format("config {}: _path", name));
|
||||
Check(Dyn::Has(loaded.config, "_path") == entry.at("hasPath").get<bool>(), std::format("config {}: _path present", name));
|
||||
std::vector<std::string> warnings;
|
||||
for (const Json& warning : Dyn::Or(Dyn::Get(loaded.config, "_warnings"), Dyn::EmptyList())) warnings.push_back(Spelled(warning.get<std::string>(), repo));
|
||||
SameJson(ToJson(warnings), entry.at("warnings"), std::format("config {}: warnings", name));
|
||||
SameJson(ToJson(Config::LensesOff(loaded.config)), entry.at("lensesOff"), std::format("config {}: lenses_off", name));
|
||||
SameJson(Config::Environments(loaded.config), entry.at("environments"), std::format("config {}: environments", name));
|
||||
}
|
||||
{
|
||||
const fs::path repo = scratch / "missing";
|
||||
fs::create_directories(repo);
|
||||
std::vector<std::string> problems;
|
||||
for (const std::string& problem : Config::Load(repo, repo / "none.json").problems) problems.push_back(Spelled(problem, repo));
|
||||
SameJson(ToJson(problems), c.at("missing"), "config --config to a missing file");
|
||||
}
|
||||
fs::remove_all(scratch);
|
||||
SameJson(Config::Defaults(), c.at("defaults"), "config DEFAULTS");
|
||||
SameJson(Config::Example(), c.at("example"), "config example()");
|
||||
SameJson(ToJson(Config::Validate(Config::Example())), c.at("exampleProblems"), "config validate(example())");
|
||||
for (const Json& entry : c.at("denied")) Check(Tools::Denied(entry.at(0).get<std::string>()) == entry.at(1).get<bool>(), std::format("denied {}", Dyn::Repr(entry.at(0).get<std::string>())));
|
||||
const Config::Slots slots = Config::SlotStacks({{"slots", {{"dir", "slots"}}}}, c.at("slotStacks").at("dir").get<std::string>());
|
||||
SameJson(slots.stacks, c.at("slotStacks").at("stacks"), "slot_stacks stacks");
|
||||
SameJson(ToJson(slots.problems), c.at("slotStacks").at("problems"), "slot_stacks problems");
|
||||
const std::array<std::int64_t, 2> oneThree = {1, 3};
|
||||
const std::array<std::int64_t, 1> one = {1};
|
||||
const std::array<std::int64_t, 1> two = {2};
|
||||
SameJson(ToJson(Config::SlotCommands({{"slots", {{"start", "up {n} {n}"}}}}, "start", oneThree)), c.at("slotCommands").at(0), "slot_commands start");
|
||||
SameJson(ToJson(Config::SlotCommands({{"slots", Json::object()}}, "stop", one)), c.at("slotCommands").at(1), "slot_commands none");
|
||||
SameJson(ToJson(Config::SlotCommands(Json::object(), "start", two)), c.at("slotCommands").at(2), "slot_commands no slots");
|
||||
for (const Json& entry : c.at("checkUrl")) SameJson(Optional(PgWire::CheckUrl(entry.at(0))), entry.at(1), std::format("check_url {}", DumpCompactSorted(entry.at(0))));
|
||||
for (const Json& entry : c.at("redact")) Same(PgWire::Redact(entry.at(0).get<std::string>()), entry.at(1).get<std::string>(), std::format("redact {}", entry.at(0).get<std::string>()));
|
||||
for (const Json& entry : c.at("where")) Same(PgWire::Where(entry.at(0).get<std::string>()), entry.at(1).get<std::string>(), std::format("where {}", entry.at(0).get<std::string>()));
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
SetHome(fs::current_path());
|
||||
const Json g = Dyn::LoadJsonFile(fs::current_path() / "tests" / "Goldens" / "fixture" / "pipeline.json");
|
||||
Same(Schema::SchemaJson("block"), g.at("schema").at("block").get<std::string>(), "schema block");
|
||||
Same(Schema::SchemaJson("whole"), g.at("schema").at("whole").get<std::string>(), "schema whole");
|
||||
for (auto it = g.at("series").begin(); it != g.at("series").end(); ++it) CheckSeries(it.key(), it.value());
|
||||
CheckPlan(g);
|
||||
CheckPrompts(g, g.at("series"));
|
||||
CheckDispositions(g);
|
||||
CheckNormalize(g);
|
||||
CheckStream(g);
|
||||
CheckState(g);
|
||||
CheckConfig(g);
|
||||
std::println("{} checks against the goldens", Checks);
|
||||
if (Failures) std::println(std::cerr, "{} failed", Failures);
|
||||
return Failures ? 1 : 0;
|
||||
}
|
||||
Loading…
Reference in a new issue