-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathtest.py
More file actions
executable file
·290 lines (219 loc) · 9.2 KB
/
test.py
File metadata and controls
executable file
·290 lines (219 loc) · 9.2 KB
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#!/usr/bin/env python
"""A python script to test the examples in the examples repo.
Run it at the root directory of the repo. It exist with an
error status code on the first error it detects.
"""
import sys
import re
import os
import json
from pathlib import Path
import subprocess
import configparser
from glob import glob
from subprocess import CompletedProcess
from typing import List, Union, Dict
# -- Examples repo top dir.
REPO_DIR = Path(".").absolute()
# -- The examples dir with the boards.
EXAMPLES_DIR = REPO_DIR / "examples"
# Valid format of board and example names.
NAME_REGEX = r"^[a-z][a-z0-9-]*$"
# List of examples that break the Verible formatter.
# TODO: Report to Verible
# TODO: Can we find a more reliable formatter.
NO_FORMAT_EXAMPLES = ["fomu/blink", "icezum/marcha-imperial"]
def read_file_lines(file: Union[str, Path]) -> List[str]:
"""Read the file, return a list of its lines rstrip'ed.."""
with open(file, "r", encoding="utf8") as f:
lines = f.readlines()
lines = [l.rstrip("\r\n") for l in lines]
return lines
def run_cmd(
cmd: List[str],
) -> CompletedProcess:
"""Run a given system command and abort if the command fails.
Returns the command process result."""
# print(f"Run: {cmd}", flush=True)
print(f"RUN: {' '.join(cmd[:2]):12}{' '.join(cmd[2:])}", flush=True)
result = subprocess.run(
cmd, text=True, capture_output=True, encoding="utf-8", check=False
)
if result.returncode != 0:
print(f"Command failed: {result.returncode}")
print(f"STDOUT: {result.stdout}")
print(f"STDERR: {result.stderr}")
sys.exit(1)
return result
def getApioBoardDefinitions() -> Dict:
"""Get from 'apio api' the list of all board definitions."""
# -- Make sure packages are updated to not contaminate the output
# -- of apio api with on the file package update.
run_cmd(["apio", "packages", "install"])
# -- Query apio for boards.
cmd_result: CompletedProcess = run_cmd(["apio", "api", "get-boards"])
# -- Parse the json text into a dict.
result = json.loads(cmd_result.stdout)
# -- Extract the "boards" section
result = result["boards"]
# -- Dump for debugging.
print(json.dumps(result, indent=4))
# -- Sanity check
assert 50 < len(result.keys()) < 500
assert "alhambra-ii" in result
# All done
return result
def test_example_info_file(info_file: Path):
"""Verify an example info file."""
# -- Read the lines of the info file.
info_lines = read_file_lines(info_file)
# -- Should have exactly one line
assert len(info_lines) == 1, "Info file should have exactly one lind."
# -- Check the info line.
info_line = info_lines[0]
assert len(info_line) >= 10, "Info line is too short"
assert len(info_line) <= 100, "Info line is too long"
assert info_line == info_line.strip(), "Info line has leading or trailing spaces "
def test_testbench_output(env_name: str, testbench: str) -> Path:
"""Assert that the testbench output files exist.."""
vcd = os.path.splitext(testbench)[0] + ".vcd"
vcd_path = Path("_build") / env_name / vcd
assert vcd_path.is_file(), f"Missing vcd file {vcd_path}"
out = os.path.splitext(testbench)[0] + ".out"
out_path = Path("_build") / env_name / out
assert out_path.is_file(), f"Missing out file {out_path}"
def test_example_env(
board_name: str,
example_name: str,
env_name: str,
source_files: List[str],
testbenches: List[str],
) -> None:
"""Test the given env of an example."""
print(f"\nENV: {board_name}/{example_name}:{env_name}")
example_dir = EXAMPLES_DIR / board_name / example_name
os.chdir(example_dir)
run_cmd(["apio", "build", "-e", env_name])
run_cmd(["apio", "lint", "-e", env_name])
run_cmd(["apio", "lint", "--nosynth", "-e", env_name])
# run_cmd(["apio", "lint", source_files[0], "-e", env_name])
run_cmd(["apio", "graph", "-n", "-e", env_name])
run_cmd(["apio", "report", "-e", env_name])
# -- Test 'apio test'
if testbenches:
run_cmd(["apio", "clean"])
run_cmd(["apio", "test", "-e", env_name])
for testbench in testbenches:
test_testbench_output(env_name, testbench)
# -- Test 'apio test --default'
if testbenches:
run_cmd(["apio", "test", "--default", "-e", env_name])
# -- Test 'apio sim' (default testbench)
if testbenches:
run_cmd(["apio", "clean"])
run_cmd(["apio", "sim", "--no-gtkwave"])
# -- Test apio sim with individual testbenches
if testbenches:
run_cmd(["apio", "clean"])
for testbench in testbenches:
run_cmd(["apio", "sim", "-e", env_name, "--no-gtkwave", testbench])
test_testbench_output(env_name, testbench)
# -- Test format.
example_key = f"{board_name}/{example_name}"
# print(f"{example_key=}")
if example_key not in NO_FORMAT_EXAMPLES:
run_cmd(["apio", "format", "-e", env_name])
else:
print("*** SKIPPING FORMAT command")
# -- Test apio api
run_cmd(["apio", "api", "get-system"])
run_cmd(["apio", "api", "get-project", "-e", env_name])
def test_example(board_name: str, example_name: str, board_defs: Dict) -> None:
"""Test an example."""
print(f"\nEXAMPLE: {board_name}/{example_name}")
assert re.match(NAME_REGEX, example_name), f"Invalid example name {example_name}"
example_dir = EXAMPLES_DIR / board_name / example_name
os.chdir(example_dir)
# -- Test the 'info file'.
test_example_info_file(example_dir / "info")
# -- Read apio.ini
apio_ini = configparser.ConfigParser()
apio_ini.read("apio.ini")
# -- Get list of project envs
sections = apio_ini.sections()
env_names = [s.split(":", 1)[1] for s in sections if s.startswith("env:")]
assert len(env_names) > 0, sections
# -- Assert that all 'board = x' match boards definitions and at
# -- least one matches this board (to allow multi board examples)
board_matches = 0
for section in apio_ini.sections():
if "board" in apio_ini[section]:
board_val = apio_ini[section]["board"]
assert board_val in board_defs
if board_val == board_name:
board_matches += 1
# -- At least one of the board specifications should be for the
# -- current board. Otherwise the example is in the wrong directory.
# -- We assume that every example contains at least one 'board = ...'
# -- specification.
assert board_matches > 0
# -- Get list of testbenches, relative to project root.
testbenches = list(Path(".").rglob("*_tb.v")) + list(Path(".").rglob("*_tb.sv"))
testbenches = [str(t) for t in testbenches]
# -- Get a list of source files, relative to project root.
source_files = list(Path(".").rglob("*.v")) + list(Path(".").rglob("*.sv"))
source_files = [str(f) for f in source_files if f not in testbenches]
assert source_files
# -- Check the testbenches files, without running them yet.
for testbench in testbenches:
# -- Assert the testbench doesn't contain $dumpfile() or VCD_OUTPUT
with open(testbench, "r", encoding="utf-8") as f:
content = f.read()
assert "$dumpfile(" not in content, f"$dumpfile() found in {testbench}"
assert "VCD_OUTPUT" not in content, f"VCD_OUTPUT found in {testbench}"
# -- Assert testbench has a GTKW signals save files.
gtkw_file = Path(testbench).with_suffix(".gtkw")
assert gtkw_file.is_file(), f"Missing .gtkw file for testbench {testbench}"
# -- Test each env
for env_name in env_names:
test_example_env(board_name, example_name, env_name, source_files, testbenches)
os.chdir(example_dir)
run_cmd(["apio", "clean"])
def test_board(board_name: str, board_defs: Dict) -> None:
"""Test board's examples."""
print("\n--------------------------")
print(f"\nBOARD: {board_name}")
assert re.match(NAME_REGEX, board_name), f"Invalid example name {board_name}"
board_dir = EXAMPLES_DIR / board_name
os.chdir(board_dir)
example_names = glob("*", root_dir=board_dir)
example_names = sorted(example_names)
assert len(example_names) > 0, f"Board dir '{board_name}' has no examples"
for example_name in example_names:
example_dir = board_dir / example_name
assert (
example_dir.is_dir()
), f"Example {board_name}/{example_name} is not a dir."
test_example(board_name, example_name, board_defs)
def main() -> None:
"""Main."""
print("Examples repo test.")
print()
print(f"{REPO_DIR=}")
print(f"{EXAMPLES_DIR=}")
# -- Get apio board definitions
board_defs: Dict = getApioBoardDefinitions()
# -- Get names of boards with examples.
board_names = glob("*", root_dir=EXAMPLES_DIR)
board_names = sorted(board_names)
assert len(board_names) > 20, f"Found too few boards: {board_names}"
# -- Test each of the boards.
for board_name in board_names:
board_dir = EXAMPLES_DIR / board_name
assert board_dir.is_dir(), f"Board dir is not a dir: {board_dir}"
# -- This may call chdir().
test_board(board_name, board_defs)
print()
print("Test completed OK.")
if __name__ == "__main__":
main()