@@ -14,8 +14,9 @@ permissions:
1414 contents : write
1515
1616env :
17- GO_VERSION : 1.21
18- CLANG_VERSION : 16
17+ GO_VERSION : " 1.21"
18+ CLANG_VERSION : " 16"
19+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 : true
1920
2021jobs :
2122 # ─────────────────────────────────────────────────────────
@@ -34,22 +35,51 @@ jobs:
3435 go-version : ${{ env.GO_VERSION }}
3536 cache : true
3637
37- - name : Install BPF toolchain and Generate
38+ - name : Install BPF toolchain
3839 run : |
39- sudo apt-get update && sudo apt-get install -y clang-${{ env.CLANG_VERSION }} llvm-${{ env.CLANG_VERSION }} libbpf-dev libelf-dev
40+ sudo apt-get update && sudo apt-get install -y \
41+ clang-${{ env.CLANG_VERSION }} \
42+ llvm-${{ env.CLANG_VERSION }} \
43+ libbpf-dev libelf-dev
4044 sudo ln -sf /usr/bin/clang-${{ env.CLANG_VERSION }} /usr/bin/clang
4145 sudo ln -sf /usr/bin/llvm-strip-${{ env.CLANG_VERSION }} /usr/bin/llvm-strip
46+ # Install bpftool — try kernel-specific, then build from source
47+ sudo apt-get install -y linux-tools-$(uname -r) 2>/dev/null || \
48+ sudo apt-get install -y linux-tools-azure 2>/dev/null || true
49+ if ! sudo bpftool version 2>/dev/null; then
50+ sudo apt-get install -y git pkg-config
51+ git clone --depth 1 --recurse-submodules \
52+ https://github.com/libbpf/bpftool.git /tmp/bpftool
53+ cd /tmp/bpftool/src && make -j$(nproc) && sudo make install prefix=/usr && cd -
54+ fi
55+
56+ - name : Generate vmlinux.h and BPF bindings
57+ run : |
58+ # Generate real vmlinux.h from kernel BTF
59+ if [ -f /sys/kernel/btf/vmlinux ]; then
60+ sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > bpf/vmlinux.h
61+ echo "✓ vmlinux.h generated ($(wc -l < bpf/vmlinux.h) lines)"
62+ else
63+ echo "ERROR: /sys/kernel/btf/vmlinux not found"; exit 1
64+ fi
4265 go install github.com/cilium/ebpf/cmd/bpf2go@v0.14.0
4366 export PATH=$PATH:$(go env GOPATH)/bin
4467 make generate
4568
46- - name : Install dependencies
47- run : sudo apt-get update && sudo apt-get install -y libbpf-dev libelf-dev
69+ - name : Check SPDX headers
70+ run : |
71+ MISSING=$(grep -rL "SPDX-License-Identifier" \
72+ --include="*.go" --include="*.c" . | grep -v vendor/ || true)
73+ if [ -n "$MISSING" ]; then
74+ echo "ERROR: Missing SPDX headers:"; echo "$MISSING"; exit 1
75+ fi
76+ echo "✓ All source files have SPDX headers"
4877
4978 - name : golangci-lint
50- uses : golangci/golangci-lint-action@v4
79+ uses : golangci/golangci-lint-action@v6
5180 with :
52- version : v1.54
81+ version : v1.59.1
82+ args : --timeout=5m --build-tags=testing
5383
5484 # ─────────────────────────────────────────────────────────
5585 # Job 2: Test — run unit tests with mock BPF
6898 cache : true
6999
70100 - name : Run tests with race detector
71- # -tags=testing ensures we use mock.go instead of loader.go
72101 run : go test -v -race -cover -tags=testing ./...
73102
74103 # ─────────────────────────────────────────────────────────
@@ -97,35 +126,53 @@ jobs:
97126 clang-${{ env.CLANG_VERSION }} \
98127 llvm-${{ env.CLANG_VERSION }} \
99128 libelf-dev \
100- libbpf-dev \
101- linux-tools-common \
102- linux-tools-generic \
103- linux-tools-$(uname -r) || true
104-
105- # Symlink clang tools to versioned binaries for bpf2go
129+ libbpf-dev
106130 sudo ln -sf /usr/bin/clang-${{ env.CLANG_VERSION }} /usr/bin/clang
107131 sudo ln -sf /usr/bin/llc-${{ env.CLANG_VERSION }} /usr/bin/llc
108132 sudo ln -sf /usr/bin/llvm-strip-${{ env.CLANG_VERSION }} /usr/bin/llvm-strip
133+ # Install bpftool
134+ sudo apt-get install -y linux-tools-$(uname -r) 2>/dev/null || \
135+ sudo apt-get install -y linux-tools-azure 2>/dev/null || true
136+ if ! sudo bpftool version 2>/dev/null; then
137+ sudo apt-get install -y git pkg-config
138+ git clone --depth 1 --recurse-submodules \
139+ https://github.com/libbpf/bpftool.git /tmp/bpftool
140+ cd /tmp/bpftool/src && make -j$(nproc) && sudo make install prefix=/usr && cd -
141+ fi
142+
143+ - name : Generate vmlinux.h
144+ run : |
145+ if [ ! -f /sys/kernel/btf/vmlinux ]; then
146+ echo "ERROR: /sys/kernel/btf/vmlinux not found"; exit 1
147+ fi
148+ sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > bpf/vmlinux.h
149+ grep -q "struct task_struct" bpf/vmlinux.h || { echo "ERROR: invalid vmlinux.h"; exit 1; }
150+ echo "✓ vmlinux.h generated ($(wc -l < bpf/vmlinux.h) lines)"
109151
110152 - name : Generate BPF and Build
111153 env :
112- CGO_ENABLED : 0
154+ CGO_ENABLED : " 0 "
113155 run : |
114- # Install bpf2go
115156 go install github.com/cilium/ebpf/cmd/bpf2go@v0.14.0
116-
117- # Use local GOPATH/bin for tools
118157 export PATH=$PATH:$(go env GOPATH)/bin
119-
120- # Generate & Build via Makefile
121- # This handles BPF compilation and Go build in one pass
122- make build VERSION=${{ github.ref_name }}
158+
159+ # FIX BUG 1: Use git describe for proper version string
160+ VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")
161+ echo "Building with VERSION=${VERSION}"
162+ make build VERSION=${VERSION}
123163
124164 - name : Verify binary
125165 run : |
126166 ls -la bin/gspy
127167 file bin/gspy
128- bin/gspy --version || true
168+ # FIX BUG 2: NO || true — if any of these fail, the build fails
169+ bin/gspy --version
170+ bin/gspy --help
171+ # Smoke test: binary must reject invalid input
172+ ! bin/gspy notapid 2>/dev/null
173+
174+ - name : Verify man page exists
175+ run : test -s man/gspy.1 || { echo "ERROR: man/gspy.1 is missing or empty"; exit 1; }
129176
130177 - name : Upload binary artifact
131178 uses : actions/upload-artifact@v4
@@ -135,12 +182,111 @@ jobs:
135182 retention-days : 30
136183
137184 # ─────────────────────────────────────────────────────────
138- # Job 4: Release — create GitHub release on tag push
185+ # Job 4: Integration test — real BPF against real process
186+ # ─────────────────────────────────────────────────────────
187+ integration :
188+ name : Integration test (real BPF)
189+ runs-on : ubuntu-latest
190+ needs : [build-linux]
191+ steps :
192+ - name : Checkout
193+ uses : actions/checkout@v4
194+
195+ - name : Set up Go
196+ uses : actions/setup-go@v5
197+ with :
198+ go-version : ${{ env.GO_VERSION }}
199+ cache : true
200+
201+ - name : Download binary artifact
202+ uses : actions/download-artifact@v4
203+ with :
204+ name : gspy-linux-amd64
205+ path : bin/
206+
207+ - name : Build test target process
208+ run : |
209+ chmod +x bin/gspy
210+ cat > /tmp/testproc.go << 'TESTEOF'
211+ package main
212+
213+ import (
214+ "net/http"
215+ "time"
216+ )
217+
218+ func main() {
219+ go func() {
220+ for {
221+ http.Get("http://127.0.0.1:19999")
222+ time.Sleep(100 * time.Millisecond)
223+ }
224+ }()
225+ select {}
226+ }
227+ TESTEOF
228+ go build -o /tmp/gspy-testproc /tmp/testproc.go
229+
230+ - name : Run integration test
231+ run : |
232+ set -euo pipefail
233+
234+ # Start the test target process
235+ /tmp/gspy-testproc &
236+ TESTPID=$!
237+ echo "Test target PID: $TESTPID"
238+ sleep 2
239+
240+ # Verify test process is running
241+ kill -0 $TESTPID || { echo "ERROR: test process not running"; exit 1; }
242+
243+ # Run gspy in JSON mode for 5 seconds
244+ sudo timeout 5 ./bin/gspy $TESTPID --json > /tmp/gspy-out.json 2>/tmp/gspy-err.txt || true
245+
246+ # Kill the test process
247+ kill $TESTPID 2>/dev/null || true
248+
249+ # Print output for debugging
250+ echo "=== gspy stdout ==="
251+ head -20 /tmp/gspy-out.json || true
252+ echo "=== gspy stderr ==="
253+ cat /tmp/gspy-err.txt || true
254+
255+ # Validate: output must exist and be non-empty
256+ if [ ! -s /tmp/gspy-out.json ]; then
257+ echo "FAIL: gspy produced no JSON output"
258+ exit 1
259+ fi
260+
261+ # At least one valid JSON line must exist with gid > 0
262+ python3 -c "
263+ import json, sys
264+ events = []
265+ with open('/tmp/gspy-out.json') as f:
266+ for line in f:
267+ line = line.strip()
268+ if line:
269+ try:
270+ events.append(json.loads(line))
271+ except:
272+ pass
273+ if not events:
274+ print('FAIL: no parseable JSON events')
275+ sys.exit(1)
276+ valid = [e for e in events if e.get('gid', 0) > 0]
277+ if not valid:
278+ print(f'FAIL: no events with gid > 0. Got {len(events)} events total.')
279+ sys.exit(1)
280+ print(f'PASS: {len(valid)} events with valid GID out of {len(events)} total')
281+ "
282+
283+ # ─────────────────────────────────────────────────────────
284+ # Job 5: Release — create GitHub release on tag push
139285 # ─────────────────────────────────────────────────────────
140286 release :
141287 name : Release
142288 runs-on : ubuntu-latest
143- needs : [build-linux, lint]
289+ needs : [build-linux, lint, integration ]
144290 if : startsWith(github.ref, 'refs/tags/v')
145291 steps :
146292 - uses : actions/checkout@v4
@@ -151,14 +297,18 @@ jobs:
151297 name : gspy-linux-amd64
152298 path : release/
153299
154- - name : Compress binary
300+ - name : Prepare release artifacts
155301 run : |
156302 cd release
157303 chmod +x gspy
158304 tar czf gspy-linux-amd64.tar.gz gspy
159305 sha256sum gspy-linux-amd64.tar.gz > SHA256SUMS.txt
160306 sha256sum gspy >> SHA256SUMS.txt
161307
308+ # FIX BUG 3: Compress and include man page in release
309+ - name : Compress man page
310+ run : gzip -k man/gspy.1
311+
162312 - name : Create GitHub Release
163313 uses : softprops/action-gh-release@v2
164314 with :
@@ -168,3 +318,4 @@ jobs:
168318 files : |
169319 release/gspy-linux-amd64.tar.gz
170320 release/SHA256SUMS.txt
321+ man/gspy.1.gz
0 commit comments