Coverage for tsfpga/yosys/test/test_common.py: 100%

91 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-09 00:40 +0000

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the tsfpga project, a project platform for modern FPGA development. 

5# https://tsfpga.com 

6# https://github.com/tsfpga/tsfpga 

7# -------------------------------------------------------------------------------------------------- 

8 

9import subprocess 

10from pathlib import Path 

11from unittest.mock import patch 

12 

13import pytest 

14 

15from tsfpga.yosys.common import ( 

16 get_ghdl_library_prefix, 

17 get_ghdl_path, 

18 get_yosys_path, 

19 run_ghdl, 

20 run_yosys, 

21 to_yosys_path, 

22) 

23 

24THIS_DIR = Path(__file__).parent 

25 

26 

27def test_run_ghdl(): 

28 ghdl_path = THIS_DIR / "ghdl.exe" 

29 arguments = ["-a", "--std=08", "apa.vhd"] 

30 expected_cmd = [str(ghdl_path.resolve()), *arguments] 

31 

32 with patch("tsfpga.yosys.common.Process") as mocked_process: 

33 mocked_process.NonZeroExitCode = ValueError 

34 assert run_ghdl(ghdl_path=ghdl_path, arguments=arguments, cwd=THIS_DIR) 

35 mocked_process.assert_called_once_with(args=expected_cmd, cwd=THIS_DIR) 

36 

37 with patch("tsfpga.yosys.common.Process") as mocked_process: 

38 mocked_process.NonZeroExitCode = ValueError 

39 mocked_process.return_value.consume_output.side_effect = ValueError("Non-zero exit code!") 

40 assert not run_ghdl(ghdl_path=ghdl_path, arguments=arguments, cwd=THIS_DIR) 

41 

42 

43def test_run_yosys(): 

44 yosys_path = THIS_DIR / "yosys.exe" 

45 ghdl_plugin_path = THIS_DIR / "ghdl.so" 

46 script_file = THIS_DIR / "apa.ys" 

47 expected_cmd = [ 

48 str(yosys_path.resolve()), 

49 "-m", 

50 str(ghdl_plugin_path.resolve()), 

51 "-s", 

52 str(script_file.resolve()), 

53 ] 

54 

55 with ( 

56 patch("tsfpga.yosys.common.Process") as mocked_process, 

57 patch("tsfpga.yosys.common.get_ghdl_library_prefix", return_value=None), 

58 ): 

59 mocked_process.NonZeroExitCode = ValueError 

60 assert run_yosys( 

61 yosys_path=yosys_path, 

62 ghdl_plugin_path=ghdl_plugin_path, 

63 script_file=script_file, 

64 cwd=THIS_DIR, 

65 ) 

66 mocked_process.assert_called_once_with(args=expected_cmd, cwd=THIS_DIR, env=None) 

67 

68 with ( 

69 patch("tsfpga.yosys.common.Process") as mocked_process, 

70 patch("tsfpga.yosys.common.get_ghdl_library_prefix", return_value=None), 

71 ): 

72 mocked_process.NonZeroExitCode = ValueError 

73 mocked_process.return_value.consume_output.side_effect = ValueError("Non-zero exit code!") 

74 assert not run_yosys( 

75 yosys_path=yosys_path, 

76 ghdl_plugin_path=None, 

77 script_file=script_file, 

78 cwd=THIS_DIR, 

79 ) 

80 

81 

82def test_run_yosys_without_plugin_does_not_add_dash_m_argument(): 

83 yosys_path = THIS_DIR / "yosys.exe" 

84 script_file = THIS_DIR / "apa.ys" 

85 expected_cmd = [str(yosys_path.resolve()), "-s", str(script_file.resolve())] 

86 

87 with ( 

88 patch("tsfpga.yosys.common.Process") as mocked_process, 

89 patch("tsfpga.yosys.common.get_ghdl_library_prefix", return_value=None), 

90 ): 

91 mocked_process.NonZeroExitCode = ValueError 

92 assert run_yosys( 

93 yosys_path=yosys_path, ghdl_plugin_path=None, script_file=script_file, cwd=THIS_DIR 

94 ) 

95 mocked_process.assert_called_once_with(args=expected_cmd, cwd=THIS_DIR, env=None) 

96 

97 

98def test_run_yosys_sets_ghdl_prefix_environment_variable(): 

99 yosys_path = THIS_DIR / "yosys.exe" 

100 script_file = THIS_DIR / "apa.ys" 

101 ghdl_prefix = THIS_DIR / "ghdl_libs" 

102 

103 with ( 

104 patch("tsfpga.yosys.common.Process") as mocked_process, 

105 patch("tsfpga.yosys.common.get_ghdl_library_prefix") as mocked_get_prefix, 

106 ): 

107 mocked_process.NonZeroExitCode = ValueError 

108 assert run_yosys( 

109 yosys_path=yosys_path, 

110 ghdl_plugin_path=None, 

111 script_file=script_file, 

112 cwd=THIS_DIR, 

113 ghdl_prefix=ghdl_prefix, 

114 ) 

115 _, kwargs = mocked_process.call_args 

116 assert kwargs["env"]["GHDL_PREFIX"] == str(ghdl_prefix.resolve()) 

117 # Explicit argument shall take precedence: auto-detection shall not even be attempted. 

118 mocked_get_prefix.assert_not_called() 

119 

120 

121def test_run_yosys_auto_detects_ghdl_prefix_environment_variable(): 

122 yosys_path = THIS_DIR / "yosys.exe" 

123 ghdl_path = THIS_DIR / "ghdl.exe" 

124 script_file = THIS_DIR / "apa.ys" 

125 ghdl_prefix = THIS_DIR / "ghdl_libs" 

126 

127 with ( 

128 patch("tsfpga.yosys.common.Process") as mocked_process, 

129 patch( 

130 "tsfpga.yosys.common.get_ghdl_library_prefix", return_value=ghdl_prefix 

131 ) as mocked_get_prefix, 

132 ): 

133 mocked_process.NonZeroExitCode = ValueError 

134 assert run_yosys( 

135 yosys_path=yosys_path, 

136 ghdl_plugin_path=None, 

137 script_file=script_file, 

138 cwd=THIS_DIR, 

139 ghdl_path=ghdl_path, 

140 ) 

141 mocked_get_prefix.assert_called_once_with(ghdl_path) 

142 _, kwargs = mocked_process.call_args 

143 assert kwargs["env"]["GHDL_PREFIX"] == str(ghdl_prefix.resolve()) 

144 

145 

146def test_get_ghdl_library_prefix(): 

147 ghdl_path = THIS_DIR / "ghdl.exe" 

148 completed_process = subprocess.CompletedProcess( 

149 args=[], 

150 returncode=0, 

151 stdout=( 

152 "command line prefix (--PREFIX): (not set)\n" 

153 "environment prefix (GHDL_PREFIX): (not set)\n" 

154 "exec prefix (from program name): /apa/ghdl\n" 

155 "library prefix: /apa/ghdl/lib/ghdl\n" 

156 ), 

157 ) 

158 with patch("tsfpga.yosys.common.subprocess.run", return_value=completed_process): 

159 assert get_ghdl_library_prefix(ghdl_path) == Path("/apa/ghdl/lib/ghdl") 

160 

161 

162def test_get_ghdl_library_prefix_returns_none_if_command_fails(): 

163 ghdl_path = THIS_DIR / "ghdl.exe" 

164 with patch( 

165 "tsfpga.yosys.common.subprocess.run", 

166 side_effect=subprocess.CalledProcessError(1, []), 

167 ): 

168 assert get_ghdl_library_prefix(ghdl_path) is None 

169 

170 

171def test_get_ghdl_library_prefix_returns_none_if_ghdl_is_not_found(): 

172 with patch("tsfpga.yosys.common.which", return_value=None): 

173 assert get_ghdl_library_prefix() is None 

174 

175 

176def test_get_ghdl_library_prefix_returns_none_if_output_can_not_be_parsed(): 

177 ghdl_path = THIS_DIR / "ghdl.exe" 

178 completed_process = subprocess.CompletedProcess(args=[], returncode=0, stdout="apa\n") 

179 with patch("tsfpga.yosys.common.subprocess.run", return_value=completed_process): 

180 assert get_ghdl_library_prefix(ghdl_path) is None 

181 

182 

183def test_get_ghdl_path_raises_exception_if_not_found_in_path(): 

184 with ( 

185 patch("tsfpga.yosys.common.which", return_value=None), 

186 pytest.raises(FileNotFoundError, match="Could not find ghdl on PATH"), 

187 ): 

188 get_ghdl_path() 

189 

190 

191def test_get_yosys_path_raises_exception_if_not_found_in_path(): 

192 with ( 

193 patch("tsfpga.yosys.common.which", return_value=None), 

194 pytest.raises(FileNotFoundError, match="Could not find yosys on PATH"), 

195 ): 

196 get_yosys_path() 

197 

198 

199def test_get_ghdl_path_with_explicit_path_does_not_check_path_env_variable(): 

200 ghdl_path = THIS_DIR / "ghdl.exe" 

201 with patch("tsfpga.yosys.common.which") as mocked_which: 

202 assert get_ghdl_path(ghdl_path) == ghdl_path.resolve() 

203 mocked_which.assert_not_called() 

204 

205 

206def test_to_yosys_path(): 

207 expected = (THIS_DIR / "apa.vhd").resolve().as_posix() 

208 assert to_yosys_path(THIS_DIR / "apa.vhd") == expected