Coverage for tests / unit_tests / scons / testing.py: 100%
37 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 02:31 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 02:31 +0000
1"""
2Helpers for apio's scons testing."""
4from typing import Optional, List
5import SCons.Script.SConsOptions
6import SCons.Node.FS
7import SCons.Environment
8import SCons.Script.Main
9from google.protobuf import text_format
10from apio.scons.apio_env import ApioEnv
11from apio.common.proto.apio_pb2 import SconsParams, TargetParams, ApioEnvParams
14TEST_PARAMS = """
15timestamp: "20123412052"
16arch: ICE40
17fpga_info {
18 fpga_id: "ice40hx4k-tq144-8k"
19 part_num: "ICE40HX4K-TQ144"
20 size: "8k"
21 ice40_params {
22 type: "hx8k"
23 package: "tq144:4k"
24 }
25}
26verbosity {
27 all: false
28 synth: false
29 pnr: false
30}
31environment {
32 platform_id: "darwin-arm64"
33 debug_level: 1
34 yosys_path: "/Users/user/.apio/packages/oss-cad-suite/share/yosys"
35 trellis_path: "/Users/user/.apio/packages/oss-cad-suite/share/trellis"
36}
37apio_env_params {
38 env_name: "default"
39 board_id: "alhambra-ii"
40 top_module: "main"
41}
42"""
45class SconsHacks:
46 """A collection of static methods that encapsulate scons access outside of
47 the official scons API. Hopefully this will not be too difficult to adapt
48 in future versions of SCons."""
50 @staticmethod
51 def reset_scons_state() -> None:
52 """Reset the relevant SCons global variables. Unfortunately scons
53 uses a few global variables to hold its state. This works well in
54 normal operation where an scons process contains a single scons
55 session but with pytest testing, where multiple independent tests
56 are running in the same process, we need to reset though variables
57 before each test."""
59 # -- The Cons.Script.Main.OptionsParser variables contains the command
60 # -- line options of scons. We reset them here and tests can access
61 # -- them using SetOption() and GetOption().
63 parser = SCons.Script.SConsOptions.Parser("my_fake_version")
64 values = SCons.Script.SConsOptions.SConsValues(
65 parser.get_default_values()
66 )
67 parser.parse_args(args=[], values=values)
68 SCons.Script.Main.OptionsParser = parser
70 # -- Reset the scons target list variable.
71 SCons.Node.FS.default_fs = None
73 # -- Clear the SCons targets
74 SCons.Environment.CleanTargets = {}
77def make_test_scons_params() -> SconsParams:
78 """Create a fake scons params for testing."""
79 return text_format.Parse(TEST_PARAMS, SconsParams())
82def make_test_apio_env(
83 *,
84 targets: Optional[List[str]] = None,
85 platform_id: str = None,
86 is_windows: bool = None,
87 debug_level: int = 0,
88 apio_env_params: ApioEnvParams = None,
89 target_params: TargetParams = None,
90) -> ApioEnv:
91 """Creates a fresh apio env for testing. The env is created
92 with the current directory as the root dir.
93 """
95 # pylint: disable=too-many-arguments
97 # -- Specify both or nether.
98 assert (platform_id is None) == (is_windows is None)
100 # -- Bring scons to a starting state.
101 SconsHacks.reset_scons_state()
103 # -- Create default params.
104 scons_params = make_test_scons_params()
106 # -- Set debug level
107 scons_params.environment.debug_level = debug_level
109 # -- Apply user overrides.
110 if platform_id is not None:
111 scons_params.environment.platform_id = platform_id
112 if is_windows is not None:
113 scons_params.environment.is_windows = is_windows
114 if apio_env_params is not None:
115 scons_params.apio_env_params.MergeFrom(apio_env_params)
116 if target_params is not None:
117 scons_params.target.MergeFrom(target_params)
119 # -- Determine scons target.
120 if targets is not None:
121 command_line_targets = targets
122 else:
123 command_line_targets = ["build"]
125 # -- Create and return the apio env.
126 return ApioEnv(
127 command_line_targets=command_line_targets, scons_params=scons_params
128 )