Skip to content

Commit 0bc7e31

Browse files
committed
test(control): share mockConn helper and add SCI PR summary bot
1 parent 5235874 commit 0bc7e31

File tree

3 files changed

+171
-87
lines changed

3 files changed

+171
-87
lines changed

.github/workflows/policy-smart-sci.yml

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ on:
2222
- ".github/workflows/policy-smart-sci.yml"
2323
workflow_dispatch:
2424

25-
permissions: read-all
25+
permissions:
26+
contents: read
27+
pull-requests: write
2628

2729
jobs:
2830
sci-1:
@@ -140,3 +142,83 @@ jobs:
140142
echo "SCI-3 failed: no tests to run"
141143
exit 1
142144
fi
145+
146+
report:
147+
name: SCI PR Comment
148+
if: always() && github.event_name == 'pull_request'
149+
needs: [sci-1, sci-2, sci-3]
150+
runs-on: ubuntu-22.04
151+
permissions:
152+
contents: read
153+
pull-requests: write
154+
steps:
155+
- name: Update PR Comment
156+
uses: actions/github-script@v7
157+
env:
158+
SCI1_RESULT: ${{ needs.sci-1.result }}
159+
SCI2_RESULT: ${{ needs.sci-2.result }}
160+
SCI3_RESULT: ${{ needs.sci-3.result }}
161+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
162+
with:
163+
script: |
164+
const marker = "<!-- dae-policy-smart-sci -->";
165+
const toIcon = (result) => {
166+
switch (result) {
167+
case "success":
168+
return "✅";
169+
case "failure":
170+
return "❌";
171+
case "cancelled":
172+
return "⚪";
173+
case "skipped":
174+
return "⏭️";
175+
default:
176+
return "❔";
177+
}
178+
};
179+
180+
const jobs = [
181+
{ name: "SCI-1 Strategy Correctness", result: process.env.SCI1_RESULT || "unknown" },
182+
{ name: "SCI-2 TCP Retry Semantics", result: process.env.SCI2_RESULT || "unknown" },
183+
{ name: "SCI-3 Concurrency Race", result: process.env.SCI3_RESULT || "unknown" },
184+
];
185+
186+
const allSuccess = jobs.every((j) => j.result === "success");
187+
const summary = allSuccess ? "pass" : "not pass";
188+
const body = [
189+
marker,
190+
"## Policy Smart SCI",
191+
"",
192+
`- Summary: **${summary}**`,
193+
`- Workflow run: ${process.env.RUN_URL}`,
194+
"",
195+
"### Job Status",
196+
"",
197+
...jobs.map((j) => `- ${toIcon(j.result)} ${j.name}: \`${j.result}\``),
198+
].join("\n");
199+
200+
const { owner, repo } = context.repo;
201+
const issue_number = context.issue.number;
202+
const comments = await github.paginate(github.rest.issues.listComments, {
203+
owner,
204+
repo,
205+
issue_number,
206+
per_page: 100,
207+
});
208+
209+
const existing = comments.find((c) => c.body && c.body.includes(marker));
210+
if (existing) {
211+
await github.rest.issues.updateComment({
212+
owner,
213+
repo,
214+
comment_id: existing.id,
215+
body,
216+
});
217+
} else {
218+
await github.rest.issues.createComment({
219+
owner,
220+
repo,
221+
issue_number,
222+
body,
223+
});
224+
}

control/mock_conn_shared_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package control
2+
3+
import (
4+
"io"
5+
"os"
6+
"sync"
7+
"time"
8+
9+
"github.com/daeuniverse/outbound/netproxy"
10+
)
11+
12+
// Ensure mockConn implements netproxy.Conn.
13+
var _ netproxy.Conn = (*mockConn)(nil)
14+
15+
// mockConn implements netproxy.Conn for control package tests.
16+
type mockConn struct {
17+
readBlock chan struct{}
18+
readRetErr error
19+
deadline time.Time
20+
mu sync.Mutex
21+
once sync.Once
22+
closed bool
23+
}
24+
25+
func newMockConn(block bool, retErr error) *mockConn {
26+
m := &mockConn{
27+
readBlock: make(chan struct{}),
28+
readRetErr: retErr,
29+
}
30+
if !block {
31+
m.once.Do(func() {
32+
close(m.readBlock)
33+
})
34+
}
35+
return m
36+
}
37+
38+
func (m *mockConn) Read(b []byte) (n int, err error) {
39+
if m.closed {
40+
return 0, io.EOF
41+
}
42+
<-m.readBlock
43+
44+
m.mu.Lock()
45+
defer m.mu.Unlock()
46+
47+
if !m.deadline.IsZero() && m.deadline.Before(time.Now()) {
48+
return 0, os.ErrDeadlineExceeded
49+
}
50+
if m.readRetErr != nil {
51+
return 0, m.readRetErr
52+
}
53+
return 0, io.EOF
54+
}
55+
56+
func (m *mockConn) Write(b []byte) (n int, err error) {
57+
return len(b), nil
58+
}
59+
60+
func (m *mockConn) Close() error {
61+
m.closed = true
62+
return nil
63+
}
64+
65+
func (m *mockConn) SetDeadline(t time.Time) error {
66+
return m.SetReadDeadline(t)
67+
}
68+
69+
func (m *mockConn) SetReadDeadline(t time.Time) error {
70+
m.mu.Lock()
71+
m.deadline = t
72+
m.mu.Unlock()
73+
74+
if !t.IsZero() && t.Before(time.Now()) {
75+
m.once.Do(func() {
76+
close(m.readBlock)
77+
})
78+
}
79+
return nil
80+
}
81+
82+
func (m *mockConn) SetWriteDeadline(time.Time) error {
83+
return nil
84+
}
85+
86+
func (m *mockConn) CloseWrite() error {
87+
return nil
88+
}

control/tcp_test.go

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -2,97 +2,11 @@ package control
22

33
import (
44
"errors"
5-
"io"
65
"os"
7-
"sync"
86
"testing"
97
"time"
10-
11-
"github.com/daeuniverse/outbound/netproxy"
128
)
139

14-
// Ensure mockConn implements netproxy.Conn
15-
var _ netproxy.Conn = (*mockConn)(nil)
16-
17-
// Mock connection implementing netproxy.Conn
18-
type mockConn struct {
19-
readBlock chan struct{}
20-
readRetErr error
21-
deadline time.Time
22-
mu sync.Mutex
23-
once sync.Once
24-
closed bool
25-
}
26-
27-
func newMockConn(block bool, retErr error) *mockConn {
28-
m := &mockConn{
29-
readBlock: make(chan struct{}),
30-
readRetErr: retErr,
31-
}
32-
if !block {
33-
m.once.Do(func() {
34-
close(m.readBlock)
35-
})
36-
}
37-
return m
38-
}
39-
40-
func (m *mockConn) Read(b []byte) (n int, err error) {
41-
if m.closed {
42-
return 0, io.EOF
43-
}
44-
<-m.readBlock
45-
46-
m.mu.Lock()
47-
defer m.mu.Unlock()
48-
49-
// Check if deadline triggered
50-
if !m.deadline.IsZero() && m.deadline.Before(time.Now()) {
51-
return 0, os.ErrDeadlineExceeded
52-
}
53-
54-
if m.readRetErr != nil {
55-
return 0, m.readRetErr
56-
}
57-
return 0, io.EOF
58-
}
59-
60-
func (m *mockConn) Write(b []byte) (n int, err error) {
61-
return len(b), nil
62-
}
63-
64-
func (m *mockConn) Close() error {
65-
m.closed = true
66-
return nil
67-
}
68-
69-
func (m *mockConn) SetDeadline(t time.Time) error {
70-
return m.SetReadDeadline(t)
71-
}
72-
73-
func (m *mockConn) SetReadDeadline(t time.Time) error {
74-
m.mu.Lock()
75-
m.deadline = t
76-
m.mu.Unlock()
77-
78-
// If deadline is in the past, unblock Read
79-
if !t.IsZero() && t.Before(time.Now()) {
80-
m.once.Do(func() {
81-
close(m.readBlock)
82-
})
83-
}
84-
return nil
85-
}
86-
87-
func (m *mockConn) SetWriteDeadline(t time.Time) error {
88-
return nil
89-
}
90-
91-
// Satisfy WriteCloser interface check in RelayTCP
92-
func (m *mockConn) CloseWrite() error {
93-
return nil
94-
}
95-
9610
func TestRelayTCP_Cancellation(t *testing.T) {
9711
// Scenario:
9812
// lConn is blocked on Read.

0 commit comments

Comments
 (0)