1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
module SomhairlesDream.Modeling.Tests.BridgeTests
open System
open System.IO
open Xunit
open SomhairlesDream.Modeling
let private withTempDirectory action =
let path = Path.Combine(Path.GetTempPath(), "somhairles-dream-bridge-" + Guid.NewGuid().ToString("N"))
Directory.CreateDirectory(path) |> ignore
try
action path
finally
if Directory.Exists(path) then
Directory.Delete(path, true)
let private fixtureScript =
[| "import argparse"
"import json"
"import os"
""
"parser = argparse.ArgumentParser()"
"parser.add_argument('--step-id', required=True)"
"parser.add_argument('--output', required=True)"
"parser.add_argument('--render-output')"
"parser.add_argument('--result-output')"
"args = parser.parse_args()"
"os.makedirs(os.path.dirname(args.output), exist_ok=True)"
"with open(args.output, 'wb') as stream:"
" stream.write(b'glTF')"
"result_path = args.result_output or args.output + '.result.json'"
"with open(result_path, 'w', encoding='utf-8') as stream:"
" json.dump({"
" 'ok': True,"
" 'blenderVersion': 'test',"
" 'exportedAt': '2026-09-20T12:00:05Z',"
" 'objectIds': ['heritage.foundation', 'heritage.deck'],"
" 'verifiedObjectIds': ['heritage.foundation', 'heritage.deck'],"
" 'glbBytes': 4"
" }, stream)"
"with open(args.output + '.log', 'w', encoding='utf-8') as stream:"
" stream.write('fixture log retained separately\\n')"
"print('Blender informational output')" |]
|> String.concat Environment.NewLine
[<Fact>]
let ``bridge reads result file instead of polluted stdout and retains process log`` () =
withTempDirectory (fun root ->
let blenderRoot = Path.Combine(root, "stage", "blender")
Directory.CreateDirectory(blenderRoot) |> ignore
File.WriteAllText(Path.Combine(blenderRoot, "fixture.py"), fixtureScript)
let output = Path.Combine(root, "run", "step.glb")
let request =
{ Step = Design.steps[0]
GlbPath = output
RenderPath = None
ResultPath = output + ".result.json"
LogPath = output + ".log" }
let result =
(BlenderProcessBridge("python3", Path.Combine(root, "stage")) :> IArtifactBridge)
.Export(request, ignore)
match result with
| Error message -> failwith message
| Ok value ->
Assert.Equal("test", value.BlenderVersion)
Assert.Equal(4L, value.GlbBytes)
Assert.True(File.Exists(output + ".log"))
Assert.Contains("Blender informational output", File.ReadAllText(output + ".log")))
|