Skip to content

Commit 91d041d

Browse files
authored
CI: Test stud-support binaries (#171)
2 parents e41d3ac + f97bb4f commit 91d041d

File tree

3 files changed

+501
-1
lines changed

3 files changed

+501
-1
lines changed

.github/workflows/debug.yml

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on: [ push, workflow_dispatch, pull_request, merge_group ]
55
jobs:
66
build:
77
name: ${{ matrix.config.name }}
8-
needs: [ build-riscv-tests ]
8+
needs: [ build-riscv-tests, build-stud-support-tests ]
99
# Avoid duplicate builds on PR from the same repository
1010
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
1111
runs-on: ${{ matrix.config.os }}
@@ -156,6 +156,32 @@ jobs:
156156
#run: python qtrvsim_tester.py --no-64 ${{ github.workspace }}/build/target/qtrvsim_cli
157157
run: python qtrvsim_tester.py -M -A --pipeline --cache ${{ github.workspace }}/build/target/qtrvsim_cli
158158

159+
- name: Get stud-support tests
160+
uses: actions/download-artifact@v4
161+
with:
162+
name: stud-support-tests
163+
path: tests/stud-support/elfs
164+
165+
- name: Stud-support tests (single cycle)
166+
if: matrix.config.os != 'ubuntu-18.04'
167+
working-directory: ${{ github.workspace }}/tests/stud-support
168+
run: python run_tests.py --qtrvsim-cli ${{ github.workspace }}/build/target/qtrvsim_cli --build-dir elfs --stud-support-path .
169+
170+
- name: Stud-support tests (pipelined)
171+
if: matrix.config.os != 'ubuntu-18.04'
172+
working-directory: ${{ github.workspace }}/tests/stud-support
173+
run: python run_tests.py --qtrvsim-cli ${{ github.workspace }}/build/target/qtrvsim_cli --build-dir elfs --stud-support-path . --pipeline
174+
175+
- name: Stud-support tests (single cycle, cached)
176+
if: matrix.config.os != 'ubuntu-18.04'
177+
working-directory: ${{ github.workspace }}/tests/stud-support
178+
run: python run_tests.py --qtrvsim-cli ${{ github.workspace }}/build/target/qtrvsim_cli --build-dir elfs --stud-support-path . --cache
179+
180+
- name: Stud-support tests (pipelined, cached)
181+
if: matrix.config.os != 'ubuntu-18.04'
182+
working-directory: ${{ github.workspace }}/tests/stud-support
183+
run: python run_tests.py --qtrvsim-cli ${{ github.workspace }}/build/target/qtrvsim_cli --build-dir elfs --stud-support-path . --pipeline --cache
184+
159185
- name: Store created artifacts
160186
uses: actions/upload-artifact@v4
161187
with:
@@ -281,3 +307,46 @@ jobs:
281307
with:
282308
name: riscv-official-tests
283309
path: tests/riscv-official/isa
310+
311+
build-stud-support-tests:
312+
name: Build stud-support tests
313+
# Avoid duplicate builds on PR from the same repository
314+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
315+
runs-on: ubuntu-22.04
316+
env:
317+
STUD_SUPPORT_REV: 53c684c05dc990e6043fcc3c9a346b2173610731
318+
steps:
319+
- uses: actions/checkout@v4
320+
with:
321+
submodules: true
322+
323+
- name: Cache stud-support tests
324+
id: cache-stud-support
325+
uses: actions/cache@v4
326+
with:
327+
path: ${{ github.workspace }}/tests/stud-support/elfs
328+
key: stud-support-tests-${{ env.STUD_SUPPORT_REV }}
329+
330+
- name: Checkout stud-support
331+
if: ${{ steps.cache-stud-support.outputs.cache-hit != 'true' }}
332+
run: |
333+
git clone https://gitlab.fel.cvut.cz/b35apo/stud-support.git stud-support
334+
cd stud-support
335+
git checkout ${{ env.STUD_SUPPORT_REV }}
336+
337+
- name: Install RISC-V toolchain for stud-support
338+
if: ${{ steps.cache-stud-support.outputs.cache-hit != 'true' }}
339+
run: |
340+
sudo apt-get update &&
341+
sudo apt-get install -y gcc-riscv64-unknown-elf
342+
343+
- name: Build stud-support tests
344+
if: ${{ steps.cache-stud-support.outputs.cache-hit != 'true' }}
345+
working-directory: ${{ github.workspace }}/tests/stud-support
346+
run: python build_tests.py --stud-support-path ../../stud-support
347+
348+
- name: Store stud-support artifacts
349+
uses: actions/upload-artifact@v4
350+
with:
351+
name: stud-support-tests
352+
path: tests/stud-support/elfs

tests/stud-support/build_tests.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Build stud-support ELFs for integration testing
4+
5+
This script compiles selected stud-support examples into RISC-V ELFs
6+
using Clang/LLVM toolchain or GCC.
7+
"""
8+
9+
import argparse
10+
import os
11+
import shutil
12+
import subprocess
13+
import sys
14+
import glob
15+
16+
# List of tests to build: (output_name, source_directory, [optional: single_file])
17+
TESTS_TO_BUILD = [
18+
("selection_sort", "seminaries/qtrvsim/selection-sort"),
19+
("vect_add", "seminaries/qtrvsim/vect-add"),
20+
("vect_add2", "seminaries/qtrvsim/vect-add2"),
21+
("vect_inc", "seminaries/qtrvsim/vect-inc"),
22+
("branchpred_1", "seminaries/qtrvsim/branchpred-1"),
23+
("ffs_as_log2", "seminaries/qtrvsim/ffs-as-log2"),
24+
("uart_echo_irq", "seminaries/qtrvsim/uart-echo-irq"),
25+
("call_clobber", "seminaries/qtrvsim/call-syscall", "lec10-01-call4-clobber.S"),
26+
("call_save", "seminaries/qtrvsim/call-syscall", "lec10-02-call4-save.S"),
27+
("fact_buggy", "seminaries/qtrvsim/call-syscall", "lec10-03-fact-buggy.S"),
28+
("fact_ok", "seminaries/qtrvsim/call-syscall", "lec10-04-fact-ok.S"),
29+
("call_10args", "seminaries/qtrvsim/call-syscall", "lec10-05-call-10args.S"),
30+
("linus_hello", "seminaries/qtrvsim/call-syscall", "lec10-06-linus-hello.S"),
31+
]
32+
33+
def get_toolchain_config(use_clang=False):
34+
"""Get toolchain configuration"""
35+
arch = os.getenv("ARCH", "riscv64-unknown-elf")
36+
37+
# Prefer GCC unless Clang is forced or GCC is missing and Clang is present
38+
if use_clang or (not shutil.which(f"{arch}-gcc") and shutil.which("clang")):
39+
cc = os.getenv("CC", "clang")
40+
cflags = os.getenv("CFLAGS", "--target=riscv32 -march=rv32g -nostdlib -static -fuse-ld=lld")
41+
return {
42+
"name": "Clang/LLVM",
43+
"cc": cc,
44+
"cflags": cflags,
45+
"ld": cc,
46+
"ldflags": cflags,
47+
"objcopy": "llvm-objcopy",
48+
"required": [cc, "ld.lld"]
49+
}
50+
51+
cc = os.getenv("CC", f"{arch}-gcc")
52+
return {
53+
"name": "GNU",
54+
"cc": cc,
55+
"cflags": os.getenv("CFLAGS", ""),
56+
"ld": f"{arch}-ld",
57+
"ldflags": "",
58+
"objcopy": f"{arch}-objcopy",
59+
"required": [cc]
60+
}
61+
62+
def check_toolchain(config):
63+
"""Check if required tools are available"""
64+
for tool in config["required"]:
65+
if not shutil.which(tool):
66+
return False, f"Tool not found: {tool}"
67+
return True, "OK"
68+
69+
def build_test(source_dir, output_name, toolchain, stud_support_root, output_dir, verbose=False, single_file=None):
70+
"""Build a single test"""
71+
full_source_dir = os.path.join(stud_support_root, source_dir)
72+
output_elf = os.path.join(output_dir, f"{output_name}.elf")
73+
74+
print(f"Building {output_name}...")
75+
if not os.path.isdir(full_source_dir):
76+
print(f" ERROR: Source directory not found: {full_source_dir}")
77+
return False, "source not found"
78+
79+
try:
80+
if single_file:
81+
source_file = os.path.join(full_source_dir, single_file)
82+
if not os.path.isfile(source_file):
83+
print(f" ERROR: Source file not found: {source_file}")
84+
return False, "source file not found"
85+
86+
_, ext = os.path.splitext(source_file)
87+
cmd = [toolchain["cc"]]
88+
if ext in ['.S', '.s']:
89+
cmd.append("-D__ASSEMBLY__")
90+
if toolchain["cflags"]:
91+
cmd.extend(toolchain["cflags"].split())
92+
elif toolchain["name"] == "GNU":
93+
# Default flags for GCC if not specified
94+
cmd.extend(["-march=rv32im", "-mabi=ilp32", "-nostdlib", "-static"])
95+
96+
cmd.extend(["-o", output_elf, source_file])
97+
98+
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
99+
if result.returncode != 0:
100+
print(f" ERROR: Build failed with return code {result.returncode}")
101+
print(" Output:")
102+
print(result.stdout)
103+
print(" Error:")
104+
print(result.stderr)
105+
return False, "build error"
106+
107+
print(f" SUCCESS: {output_elf}")
108+
return True, None
109+
110+
else:
111+
# Makefile build
112+
subprocess.run(["make", "clean"], cwd=full_source_dir, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False)
113+
114+
make_vars = [
115+
f"CC={toolchain['cc']}",
116+
f"AS={toolchain['cc']}",
117+
f"CXX={toolchain['cc']}",
118+
f"LD={toolchain['ld']}",
119+
f"OBJCOPY={toolchain['objcopy']}",
120+
f"LOADLIBES="
121+
]
122+
123+
if toolchain['cflags']:
124+
make_vars.extend([
125+
f"CFLAGS={toolchain['cflags']}",
126+
f"AFLAGS={toolchain['cflags']}",
127+
f"CXXFLAGS={toolchain['cflags']}"
128+
])
129+
130+
if toolchain['ldflags']:
131+
make_vars.append(f"LDFLAGS={toolchain['ldflags']}")
132+
133+
result = subprocess.run(["make"] + make_vars, cwd=full_source_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
134+
if result.returncode != 0:
135+
print(f" ERROR: Build failed with return code {result.returncode}")
136+
print(" Output:")
137+
print(result.stdout)
138+
print(" Error:")
139+
print(result.stderr)
140+
return False, "build error"
141+
142+
# Find built ELF
143+
candidates = [os.path.join(full_source_dir, os.path.basename(source_dir))] + \
144+
glob.glob(os.path.join(full_source_dir, "*.elf"))
145+
146+
for candidate in candidates:
147+
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
148+
# Simple check if it looks like an ELF (starts with \x7fELF)
149+
with open(candidate, 'rb') as f:
150+
if f.read(4) == b'\x7fELF':
151+
shutil.copy2(candidate, output_elf)
152+
print(f" SUCCESS: {output_elf}")
153+
return True, None
154+
155+
print(f" ERROR: Built ELF not found in {full_source_dir}")
156+
return False, "ELF not found"
157+
158+
except Exception as e:
159+
print(f" ERROR: Exception: {e}")
160+
return False, f"exception: {e}"
161+
162+
def main():
163+
parser = argparse.ArgumentParser(description="Build stud-support ELFs")
164+
parser.add_argument("--verbose", "-v", action="store_true", help="Show detailed output")
165+
parser.add_argument("--use-gcc", action="store_true", help="Force GNU toolchain")
166+
parser.add_argument("--use-clang", action="store_true", help="Force Clang toolchain")
167+
parser.add_argument("--output-dir", help="Output directory")
168+
parser.add_argument("--stud-support-path", required=True, help="Path to stud-support repo")
169+
args = parser.parse_args()
170+
171+
output_dir = args.output_dir or os.path.join(os.path.dirname(os.path.realpath(__file__)), "elfs")
172+
if not os.path.isdir(args.stud_support_path):
173+
print(f"Error: stud-support not found at {args.stud_support_path}")
174+
return 1
175+
176+
toolchain = get_toolchain_config(args.use_clang)
177+
if args.use_gcc and toolchain["name"] != "GNU":
178+
# User forced GCC but we defaulted to Clang (shouldn't happen with current logic but good to be safe)
179+
toolchain = get_toolchain_config(False)
180+
181+
available, msg = check_toolchain(toolchain)
182+
if not available:
183+
print(f"Error: {msg}\nPlease install riscv64-unknown-elf-gcc or clang/lld.")
184+
return 1
185+
186+
os.makedirs(output_dir, exist_ok=True)
187+
print(f"Using toolchain: {toolchain['name']} ({toolchain['cc']})")
188+
print(f"Output: {output_dir}\n")
189+
190+
results = [build_test(t[1], t[0], toolchain, args.stud_support_path, output_dir, args.verbose, t[2] if len(t)>2 else None) for t in TESTS_TO_BUILD]
191+
success_count = sum(1 for r in results if r[0])
192+
193+
print(f"\nBuild Summary: {success_count}/{len(TESTS_TO_BUILD)} successful")
194+
if success_count < len(TESTS_TO_BUILD):
195+
print("Failed tests:")
196+
for i, (success, reason) in enumerate(results):
197+
if not success: print(f" - {TESTS_TO_BUILD[i][0]} ({reason})")
198+
return 1
199+
200+
return 0
201+
202+
if __name__ == "__main__":
203+
sys.exit(main())

0 commit comments

Comments
 (0)