| 1 | module builder |
| 2 | |
| 3 | import os |
| 4 | import v.pref |
| 5 | |
| 6 | fn restore_env_var(name string, old_value ?string) { |
| 7 | if value := old_value { |
| 8 | os.setenv(name, value, true) |
| 9 | } else { |
| 10 | os.unsetenv(name) |
| 11 | } |
| 12 | } |
| 13 | |
| 14 | fn restore_c_error_bug_report_url_env(old_url ?string) { |
| 15 | restore_env_var('V_C_ERROR_BUG_REPORT_URL', old_url) |
| 16 | } |
| 17 | |
| 18 | fn restore_c_error_bug_report_disabled_env(old_value ?string) { |
| 19 | restore_env_var(c_error_bug_report_disabled_env, old_value) |
| 20 | } |
| 21 | |
| 22 | fn test_c_error_location_for_generated_c_parses_gcc_output() { |
| 23 | loc := c_error_location_for_generated_c('/tmp/program.tmp.c:42:7: error: unknown type name', |
| 24 | '/tmp/program.tmp.c') or { |
| 25 | assert false |
| 26 | return |
| 27 | } |
| 28 | assert loc.line == 42 |
| 29 | } |
| 30 | |
| 31 | fn test_c_error_location_for_generated_c_parses_msvc_output() { |
| 32 | loc := c_error_location_for_generated_c('C:\\tmp\\program.tmp.c(19): error C2143: syntax error', |
| 33 | 'C:\\tmp\\program.tmp.c') or { |
| 34 | assert false |
| 35 | return |
| 36 | } |
| 37 | assert loc.line == 19 |
| 38 | } |
| 39 | |
| 40 | fn test_numbered_context_lines_returns_five_lines_each_side() { |
| 41 | lines := ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'] |
| 42 | context := numbered_context_lines(lines, 6, 5) |
| 43 | assert context.len == 11 |
| 44 | assert context.first().line == 1 |
| 45 | assert context.last().line == 11 |
| 46 | assert context[5].line == 6 |
| 47 | assert context[5].text == '6' |
| 48 | } |
| 49 | |
| 50 | fn test_v_source_location_mapping_from_line_directives() { |
| 51 | c_lines := [ |
| 52 | '#line 10 "/tmp/source.v"', |
| 53 | 'int a = 1;', |
| 54 | 'int b = missing;', |
| 55 | '#line 999 "/tmp/program.tmp.c"', |
| 56 | 'int main(void) { return 0; }', |
| 57 | ] |
| 58 | loc := v_source_location_for_c_line(c_lines, 3, '/tmp/program.tmp.c') or { |
| 59 | assert false |
| 60 | return |
| 61 | } |
| 62 | assert loc.file == '/tmp/source.v' |
| 63 | assert loc.line == 11 |
| 64 | c_line := generated_c_line_for_source_location(c_lines, CErrorReportLocation{ |
| 65 | file: '/tmp/source.v' |
| 66 | line: 11 |
| 67 | }, '/tmp/program.tmp.c') or { |
| 68 | assert false |
| 69 | return |
| 70 | } |
| 71 | assert c_line == 3 |
| 72 | } |
| 73 | |
| 74 | fn test_generated_c_reset_line_is_not_reported_as_v_source() { |
| 75 | c_lines := [ |
| 76 | '#line 40 "/tmp/program.tmp.c"', |
| 77 | 'int generated = missing;', |
| 78 | ] |
| 79 | if _ := v_source_location_for_c_line(c_lines, 2, '/tmp/program.tmp.c') { |
| 80 | assert false |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | fn test_generated_c_line_for_source_location_prefers_non_empty_line() { |
| 85 | c_lines := [ |
| 86 | '#line 5 "/tmp/source.v"', |
| 87 | 'void main__main(void) {', |
| 88 | '', |
| 89 | '#line 6 "/tmp/source.v"', |
| 90 | '{NoSuchType _ = ((NoSuchType){E_STRUCT});}', |
| 91 | '}', |
| 92 | ] |
| 93 | c_line := generated_c_line_for_source_location(c_lines, CErrorReportLocation{ |
| 94 | file: '/tmp/source.v' |
| 95 | line: 6 |
| 96 | }, '/tmp/program.tmp.c') or { |
| 97 | assert false |
| 98 | return |
| 99 | } |
| 100 | assert c_line == 5 |
| 101 | } |
| 102 | |
| 103 | fn test_c_error_bug_report_url_uses_override_without_trailing_slash() { |
| 104 | assert c_error_bug_report_url(' http://127.0.0.1:19090/bug-report/ ') == 'http://127.0.0.1:19090/bug-report' |
| 105 | } |
| 106 | |
| 107 | fn test_c_error_bug_report_url_uses_bugs_domain_by_default() { |
| 108 | old_url := os.getenv_opt('V_C_ERROR_BUG_REPORT_URL') |
| 109 | os.unsetenv('V_C_ERROR_BUG_REPORT_URL') |
| 110 | defer { |
| 111 | restore_c_error_bug_report_url_env(old_url) |
| 112 | } |
| 113 | assert c_error_bug_report_url('') == 'https://bugs.vlang.io/bug-report' |
| 114 | } |
| 115 | |
| 116 | fn test_should_submit_c_error_bug_report_allows_default_outside_github_ci() { |
| 117 | old_github_actions := os.getenv_opt('GITHUB_ACTIONS') |
| 118 | old_github_job := os.getenv_opt('GITHUB_JOB') |
| 119 | old_disabled := os.getenv_opt(c_error_bug_report_disabled_env) |
| 120 | os.unsetenv('GITHUB_ACTIONS') |
| 121 | os.unsetenv('GITHUB_JOB') |
| 122 | os.unsetenv(c_error_bug_report_disabled_env) |
| 123 | defer { |
| 124 | restore_env_var('GITHUB_ACTIONS', old_github_actions) |
| 125 | restore_env_var('GITHUB_JOB', old_github_job) |
| 126 | restore_c_error_bug_report_disabled_env(old_disabled) |
| 127 | } |
| 128 | assert should_submit_c_error_bug_report('') |
| 129 | } |
| 130 | |
| 131 | fn test_should_submit_c_error_bug_report_skips_bugs_domain_in_github_ci() { |
| 132 | old_github_actions := os.getenv_opt('GITHUB_ACTIONS') |
| 133 | old_github_job := os.getenv_opt('GITHUB_JOB') |
| 134 | old_url := os.getenv_opt('V_C_ERROR_BUG_REPORT_URL') |
| 135 | old_disabled := os.getenv_opt(c_error_bug_report_disabled_env) |
| 136 | os.setenv('GITHUB_ACTIONS', 'true', true) |
| 137 | os.unsetenv('GITHUB_JOB') |
| 138 | os.unsetenv('V_C_ERROR_BUG_REPORT_URL') |
| 139 | os.unsetenv(c_error_bug_report_disabled_env) |
| 140 | defer { |
| 141 | restore_env_var('GITHUB_ACTIONS', old_github_actions) |
| 142 | restore_env_var('GITHUB_JOB', old_github_job) |
| 143 | restore_c_error_bug_report_url_env(old_url) |
| 144 | restore_c_error_bug_report_disabled_env(old_disabled) |
| 145 | } |
| 146 | assert !should_submit_c_error_bug_report('') |
| 147 | assert !should_submit_c_error_bug_report(' https://bugs.vlang.io/bug-report/ ') |
| 148 | } |
| 149 | |
| 150 | fn test_should_submit_c_error_bug_report_uses_custom_url_in_github_ci() { |
| 151 | old_github_actions := os.getenv_opt('GITHUB_ACTIONS') |
| 152 | old_github_job := os.getenv_opt('GITHUB_JOB') |
| 153 | old_url := os.getenv_opt('V_C_ERROR_BUG_REPORT_URL') |
| 154 | old_disabled := os.getenv_opt(c_error_bug_report_disabled_env) |
| 155 | os.unsetenv('GITHUB_ACTIONS') |
| 156 | os.setenv('GITHUB_JOB', 'test', true) |
| 157 | os.setenv('V_C_ERROR_BUG_REPORT_URL', 'http://127.0.0.1:19090/bug-report', true) |
| 158 | os.unsetenv(c_error_bug_report_disabled_env) |
| 159 | defer { |
| 160 | restore_env_var('GITHUB_ACTIONS', old_github_actions) |
| 161 | restore_env_var('GITHUB_JOB', old_github_job) |
| 162 | restore_c_error_bug_report_url_env(old_url) |
| 163 | restore_c_error_bug_report_disabled_env(old_disabled) |
| 164 | } |
| 165 | assert should_submit_c_error_bug_report('') |
| 166 | assert should_submit_c_error_bug_report('http://127.0.0.1:19091/bug-report') |
| 167 | } |
| 168 | |
| 169 | fn test_should_submit_c_error_bug_report_can_be_disabled_by_env() { |
| 170 | old_github_actions := os.getenv_opt('GITHUB_ACTIONS') |
| 171 | old_github_job := os.getenv_opt('GITHUB_JOB') |
| 172 | old_disabled := os.getenv_opt(c_error_bug_report_disabled_env) |
| 173 | os.unsetenv('GITHUB_ACTIONS') |
| 174 | os.unsetenv('GITHUB_JOB') |
| 175 | defer { |
| 176 | restore_env_var('GITHUB_ACTIONS', old_github_actions) |
| 177 | restore_env_var('GITHUB_JOB', old_github_job) |
| 178 | restore_c_error_bug_report_disabled_env(old_disabled) |
| 179 | } |
| 180 | for value in ['1', 'true', 'yes', 'on'] { |
| 181 | os.setenv(c_error_bug_report_disabled_env, value, true) |
| 182 | assert !should_submit_c_error_bug_report('') |
| 183 | assert !should_submit_c_error_bug_report('http://127.0.0.1:19090/bug-report') |
| 184 | } |
| 185 | os.setenv(c_error_bug_report_disabled_env, '0', true) |
| 186 | assert should_submit_c_error_bug_report('') |
| 187 | disable_c_error_bug_reports() |
| 188 | assert !should_submit_c_error_bug_report('') |
| 189 | } |
| 190 | |
| 191 | fn test_bounded_c_error_bug_report_keeps_encoded_body_under_limit() { |
| 192 | long_output := 'C compiler diagnostic '.repeat(12000) |
| 193 | long_c_line := 'generated C line '.repeat(1000) |
| 194 | long_v_line := 'source V line '.repeat(1000) |
| 195 | report := CErrorBugReport{ |
| 196 | kind: 'v-c-compiler-error' |
| 197 | v_version: 'V test' |
| 198 | target_os: 'linux' |
| 199 | target_backend: 'c' |
| 200 | ccompiler: 'cc' |
| 201 | c_error: long_output |
| 202 | c_file: '/tmp/program.tmp.c' |
| 203 | c_line: 12 |
| 204 | c_context: [ |
| 205 | CErrorReportLine{ |
| 206 | line: 12 |
| 207 | text: long_c_line |
| 208 | }, |
| 209 | ] |
| 210 | v_file: '/tmp/source.v' |
| 211 | v_line: 4 |
| 212 | v_context: [ |
| 213 | CErrorReportLine{ |
| 214 | line: 4 |
| 215 | text: long_v_line |
| 216 | }, |
| 217 | ] |
| 218 | } |
| 219 | bounded := bounded_c_error_bug_report(report, 4096) |
| 220 | encoded := c_error_bug_report_json(bounded) |
| 221 | assert encoded.len <= 4096 |
| 222 | assert bounded.c_error.len < report.c_error.len |
| 223 | assert bounded.c_context[0].line == 12 |
| 224 | assert bounded.c_context[0].text.len < report.c_context[0].text.len |
| 225 | assert bounded.v_context[0].line == 4 |
| 226 | assert bounded.v_context[0].text.len < report.v_context[0].text.len |
| 227 | } |
| 228 | |
| 229 | fn test_c_error_bug_report_json_escapes_strings() { |
| 230 | report := CErrorBugReport{ |
| 231 | kind: 'v-c-compiler-error' |
| 232 | v_version: 'V "test"\n' |
| 233 | c_context: [ |
| 234 | CErrorReportLine{ |
| 235 | line: 1 |
| 236 | text: 'tab\tslash\\' |
| 237 | }, |
| 238 | ] |
| 239 | } |
| 240 | encoded := c_error_bug_report_json(report) |
| 241 | assert encoded.contains('"v_version":"V \\"test\\"\\n"') |
| 242 | assert encoded.contains('"text":"tab\\tslash\\\\"') |
| 243 | } |
| 244 | |
| 245 | fn test_truncated_report_text_preserves_start_and_end_when_space_allows() { |
| 246 | text := 'start-' + 'x'.repeat(100) + '-end' |
| 247 | truncated := truncated_report_text(text, 80) |
| 248 | assert truncated.len <= 80 |
| 249 | assert truncated.starts_with('start-') |
| 250 | assert truncated.contains('report truncated before upload') |
| 251 | assert truncated.ends_with('-end') |
| 252 | } |
| 253 | |
| 254 | fn test_new_c_error_bug_report_with_vlines_is_skipped_when_already_vlines() { |
| 255 | // When the program is already compiled with -g, the original `.tmp.c` already has |
| 256 | // `#line` directives, so there is nothing to regenerate. |
| 257 | mut b := Builder{ |
| 258 | pref: &pref.Preferences{ |
| 259 | is_vlines: true |
| 260 | } |
| 261 | } |
| 262 | if _ := b.new_c_error_bug_report_with_vlines('cc') { |
| 263 | assert false, 'expected none when the C source is already #line annotated' |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | fn test_new_c_error_bug_report_with_vlines_is_skipped_without_a_recorded_command() { |
| 268 | // Without a recorded C compiler command (e.g. -parallel-cc, or a Windows MSVC build), |
| 269 | // there is no command to rerun, so no V mapping can be produced this way. |
| 270 | mut b := Builder{ |
| 271 | pref: &pref.Preferences{} |
| 272 | last_cc_cmd: '' |
| 273 | } |
| 274 | if _ := b.new_c_error_bug_report_with_vlines('cc') { |
| 275 | assert false, 'expected none when no C compiler command was recorded' |
| 276 | } |
| 277 | } |
| 278 | |