返回 DeepSeek-Reasonix
isolation_regression_test.go
根目录 / internal / attachment / isolation_regression_test.go
1 package attachment
2
3 import (
4 "context"
5 "os"
6 "path/filepath"
7 "sync"
8 "testing"
9 )
10
11 func TestAttachmentParentSymlinkCannotEscape(t *testing.T) {
12 root, outside := t.TempDir(), t.TempDir()
13 if err := os.MkdirAll(filepath.Join(root, ".reasonix"), 0700); err != nil {
14 t.Fatal(err)
15 }
16 if err := os.WriteFile(filepath.Join(outside, "secret.png"), opaquePNG(t, 8, 8), 0600); err != nil {
17 t.Fatal(err)
18 }
19 if err := os.Symlink(outside, filepath.Join(root, ".reasonix", "attachments")); err != nil {
20 t.Fatal(err)
21 }
22 _, err := testService(t).PrepareBatch(t.Context(), []Source{{WorkspaceRoot: root, Path: ".reasonix/attachments/secret.png", Confine: ".reasonix/attachments"}})
23 if err == nil {
24 t.Fatal("accepted image outside workspace through parent symlink")
25 }
26 }
27
28 // Done is first consulted by share after it has registered this waiter.
29 type joinedVariantContext struct {
30 context.Context
31 joined chan struct{}
32 once sync.Once
33 }
34
35 func (c *joinedVariantContext) Done() <-chan struct{} {
36 c.once.Do(func() { close(c.joined) })
37 return c.Context.Done()
38 }
39
40 func TestVariantWaitersCancelIndependently(t *testing.T) {
41 cache := NewVariantCache(DefaultCacheBytes, 2)
42 key := variantKey{digest: "independent"}
43 started, release := make(chan struct{}), make(chan struct{})
44 ctx, cancel := context.WithCancel(t.Context())
45 first := make(chan error, 1)
46 go func() {
47 _, err := cache.share(ctx, key, func(work context.Context) (Variant, error) {
48 close(started)
49 <-release
50 return Variant{Bytes: []byte("shared")}, work.Err()
51 })
52 first <- err
53 }()
54 <-started
55 secondCtx := &joinedVariantContext{Context: t.Context(), joined: make(chan struct{})}
56 second := make(chan error, 1)
57 go func() {
58 value, err := cache.share(secondCtx, key, func(context.Context) (Variant, error) {
59 t.Error("second waiter started a second transform")
60 return Variant{}, nil
61 })
62 if err == nil && string(value.Bytes) != "shared" {
63 t.Error("wrong shared result")
64 }
65 second <- err
66 }()
67 <-secondCtx.joined
68 cancel()
69 if err := <-first; !Is(err, CodeCanceled) {
70 t.Fatalf("first waiter = %v", err)
71 }
72 close(release)
73 if err := <-second; err != nil {
74 t.Fatalf("second waiter canceled by first: %v", err)
75 }
76 }
77
78 func TestCanceledVariantCannotCaptureSuccessor(t *testing.T) {
79 cache := NewVariantCache(DefaultCacheBytes, 2)
80 key := variantKey{digest: "test"}
81 started, finish, returned := make(chan struct{}), make(chan struct{}), make(chan struct{})
82 ctx, cancel := context.WithCancel(t.Context())
83 go func() {
84 defer close(returned)
85 _, _ = cache.share(ctx, key, func(workCtx context.Context) (Variant, error) {
86 close(started)
87 <-finish
88 return Variant{}, workCtx.Err()
89 })
90 }()
91 <-started
92 cancel()
93 <-returned
94 defer close(finish)
95 result, err := cache.share(t.Context(), key, func(context.Context) (Variant, error) {
96 return Variant{Bytes: []byte("successor")}, nil
97 })
98 if err != nil || string(result.Bytes) != "successor" {
99 t.Fatalf("successor result: %q, %v", result.Bytes, err)
100 }
101 }
102
102 lines GO