Coverage for tests/unit_tests/scons/testing.py: 100%

35 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-06 10:20 +0000

1""" 

2Helpers for apio's scons testing.""" 

3 

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 

12 

13 

14TEST_PARAMS = """ 

15timestamp: "20123412052" 

16arch: ICE40 

17fpga_info { 

18 fpga_id: "ice40hx4k-tq144-8k" 

19 part_num: "ICE40HX4K-TQ144" 

20 size: "8k" 

21 ice40 { 

22 type: "hx8k" 

23 pack: "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""" 

43 

44 

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.""" 

49 

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.""" 

58 

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(). 

62 

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 

69 

70 # -- Reset the scons target list variable. 

71 SCons.Node.FS.default_fs = None 

72 

73 # -- Clear the SCons targets 

74 SCons.Environment.CleanTargets = {} 

75 

76 

77def make_test_scons_params() -> SconsParams: 

78 """Create a fake scons params for testing.""" 

79 return text_format.Parse(TEST_PARAMS, SconsParams()) 

80 

81 

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 target_params: TargetParams = None, 

89) -> ApioEnv: 

90 """Creates a fresh apio env for testing. The env is created 

91 with the current directory as the root dir. 

92 """ 

93 # -- Specify both or nether. 

94 assert (platform_id is None) == (is_windows is None) 

95 

96 # -- Bring scons to a starting state. 

97 SconsHacks.reset_scons_state() 

98 

99 # -- Create default params. 

100 scons_params = make_test_scons_params() 

101 

102 # -- Set debug level 

103 scons_params.environment.debug_level = debug_level 

104 

105 # -- Apply user overrides. 

106 if platform_id is not None: 

107 scons_params.environment.platform_id = platform_id 

108 if is_windows is not None: 

109 scons_params.environment.is_windows = is_windows 

110 if target_params is not None: 

111 scons_params.target.MergeFrom(target_params) 

112 

113 # -- Determine scons target. 

114 if targets is not None: 

115 command_line_targets = targets 

116 else: 

117 command_line_targets = ["build"] 

118 

119 # -- Create and return the apio env. 

120 return ApioEnv( 

121 command_line_targets=command_line_targets, scons_params=scons_params 

122 )