* test(library): read one "now" in the fixture and the page across midnight The concept fixture recorded each prompt's last use with the database's wall clock at seeding, while the page computed "today" from its own clock at render. A run seeded at 23:59:5x UTC and rendered after midnight read "Used yesterday" against the concept's "Used today" (PR #284, frontend coverage shard 2/3). The concept fixture now takes one instant (optional "now" through the concept reset hook, the wall clock otherwise) for every last use, mtime and run time it writes and returns it as manifest.now; seedConceptAt fixes the browser's Date at that instant. The Library and Images concept specs seed through it, and the two workspace specs that assert "Used today" and the "Today" caption fix the page clock at the time they patch or seed. A new test starts the browser at 23:59:58 UTC, seeds at that instant and passes midnight before either document opens. * test(activity): judge the lost-server backoff by its gaps, not a timed window "lost server clears the pill and marks jobs ended" counted failed job-list re-reads in a 3.5 s waitForTimeout window that started once the test saw the ended job. Re-reads back off 1 s, 2 s, then 4 s, so the third lands 4 s after the second. A runner that noticed the ended job more than 0.5 s late moved the window over the third read (firefox shard 1/2 on #287: 3 reads, expected at most 2). The test now waits for the third failed read and asserts the gaps between reads: at least 2 s, then at least 4 s, with 100 ms allowed for the request being reported late. * test(activity): replace the spec's remaining timer waits with gaps and a run-out clock Three checks in workspace-activity.spec.mjs waited on wall-clock timers: - "lost server": waitForTimeout(2500), then at most 2 list reads. The read after the one that found the restart must be the periodic poll, so the test now waits for it and asserts the gap is at least POLL_MS (less 100 ms for late request reporting). Any extra read lands in that gap. - "200-row cap note" (review M12): waitForTimeout(1500), then no detail read. The page's timers are now Playwright's clock. The test runs one read-throttle interval out, so any read the counter update scheduled is sent before the log opens. Once the log's read has answered, exactly one read must be listed. - "busy start" (review M14): waitForTimeout(7000), then the link toast is still visible. The test now runs the page's clock 7 s on, which fires a 6.5 s clear however slowly the runner got there. RED, each against a temporary product change: POLL_MS 3000 (gap 3004 ms, expected at least 14900), detail re-read on new log lines while the log is closed (an unexpected read), LINK_TOAST_MS 6500 (toast link gone), and BACKOFF_FIRST_MS 200 for the earlier backoff check (gap 407 ms, expected at least 1900).
113 lines
4.5 KiB
Python
113 lines
4.5 KiB
Python
"""The concept fixture files carry the concept inspector facts (app.js:400-414)."""
|
|
|
|
import json
|
|
import math
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
try:
|
|
from tests.open_handles import assert_closed
|
|
except ImportError: # discovered with tests/ as the top-level directory
|
|
from open_handles import assert_closed
|
|
|
|
from tests.browser.workspace_concept_fixture import (
|
|
CONCEPT_MODEL,
|
|
CONCEPT_PROMPTS,
|
|
MAX_EPOCH_SECONDS,
|
|
_instant,
|
|
write_concept_png,
|
|
)
|
|
from utils.prompt_inspector import inspect_prompt_evidence, read_inspection_file
|
|
|
|
|
|
class TestWorkspaceConceptFixture(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.tmp.cleanup)
|
|
self.addCleanup(assert_closed, self, self.tmp.name)
|
|
|
|
def write(self, prompt_index, round_index):
|
|
prompt = CONCEPT_PROMPTS[prompt_index]
|
|
path = Path(self.tmp.name) / f"{prompt.id}_{round_index + 1:05}.png"
|
|
image = write_concept_png(path, prompt_index, round_index)
|
|
return path, image
|
|
|
|
def test_each_image_carries_execution_evidence_and_the_concept_facts(self):
|
|
for prompt_index, round_index in ((0, 0), (0, 2), (3, 1), (5, 3)):
|
|
with self.subTest(prompt=prompt_index, round=round_index):
|
|
prompt = CONCEPT_PROMPTS[prompt_index]
|
|
path, image = self.write(prompt_index, round_index)
|
|
negative = prompt.negatives[round_index % len(prompt.negatives)][1]
|
|
inspection = read_inspection_file(path)
|
|
evidence = inspect_prompt_evidence(inspection.metadata)
|
|
# D21: "execution" is the source the panel badges "Execution recorded".
|
|
self.assertEqual(
|
|
evidence["prompts"]["positive"],
|
|
{
|
|
"status": "resolved",
|
|
"text": prompt.positive,
|
|
"source": "execution",
|
|
},
|
|
)
|
|
self.assertEqual(evidence["prompts"]["negative"]["text"], negative)
|
|
self.assertEqual(evidence["prompts"]["negative"]["source"], "execution")
|
|
graph = json.loads(inspection.metadata["prompt"])
|
|
sampler = next(
|
|
node["inputs"]
|
|
for node in graph.values()
|
|
if node["class_type"] == "KSampler"
|
|
)
|
|
self.assertEqual(
|
|
sampler["seed"], 42001 + prompt_index * 100 + round_index
|
|
)
|
|
self.assertEqual(sampler["seed"], image["seed"])
|
|
self.assertEqual(
|
|
(sampler["sampler_name"], sampler["scheduler"]),
|
|
("dpmpp_2m", "karras"),
|
|
)
|
|
self.assertEqual((sampler["steps"], sampler["cfg"]), (28, 6.5))
|
|
loader = graph[sampler["model"][0]]
|
|
self.assertEqual(loader["class_type"], "CheckpointLoaderSimple")
|
|
self.assertEqual(loader["inputs"]["ckpt_name"], CONCEPT_MODEL)
|
|
self.assertEqual(CONCEPT_MODEL, "illustrative-model.safetensors")
|
|
with Image.open(path) as source:
|
|
self.assertEqual(source.size, (image["width"], image["height"]))
|
|
|
|
def test_concept_dimensions_follow_the_prompt_index(self):
|
|
expected = [(1536, 1152), (1024, 1536), (1280, 1280)]
|
|
for prompt_index, size in enumerate(expected):
|
|
_, image = self.write(prompt_index, 1)
|
|
self.assertEqual((image["width"], image["height"]), size)
|
|
|
|
|
|
class TestConceptFixtureInstant(unittest.TestCase):
|
|
"""One "now" for every relative time the fixture writes (PR #284's midnight)."""
|
|
|
|
def test_a_chosen_instant_is_kept(self):
|
|
self.assertEqual(_instant(1_792_713_598), 1_792_713_598.0)
|
|
self.assertEqual(_instant(1_792_713_598.25), 1_792_713_598.25)
|
|
|
|
def test_the_wall_clock_when_omitted(self):
|
|
before = time.time()
|
|
now = _instant(None)
|
|
self.assertLessEqual(before, now)
|
|
self.assertLessEqual(now, time.time())
|
|
|
|
def test_a_non_number_is_refused(self):
|
|
for value in (True, "1792713598", [1]):
|
|
with self.subTest(value=value), self.assertRaises(TypeError):
|
|
_instant(value)
|
|
|
|
def test_an_instant_out_of_range_is_refused(self):
|
|
for value in (0, -1, MAX_EPOCH_SECONDS, math.inf, math.nan):
|
|
with self.subTest(value=value), self.assertRaises(ValueError):
|
|
_instant(value)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|