vxx2 / vlib / v / builder / builder_test.v
557 lines · 487 sloc · 19.18 KB · 42fc2c0dcbfb385eb6fbbb5723a2cad01db1cf81
Raw
1module main
2
3import os
4import v.builder
5import v.pref
6
7const vexe = @VEXE
8const test_path = os.join_path(os.vtmp_dir(), 'run_check')
9
10fn testsuite_begin() {
11 os.mkdir_all(test_path) or {}
12}
13
14fn testsuite_end() {
15 os.rmdir_all(test_path) or {}
16}
17
18fn run_v_ok(command string) string {
19 res := os.execute(command)
20 if res.exit_code != 0 {
21 eprintln('> failing cmd: ${command}')
22 eprintln('> output:\n${res.output}')
23 assert res.exit_code == 0
24 }
25 return res.output
26}
27
28fn test_conditional_executable_removal() {
29 os.chdir(test_path)!
30 os.write_file('main.v', 'fn main(){\n\tprintln("Hello World!")\n}\n')!
31
32 mut executable := 'run_check'
33 $if windows {
34 executable += '.exe'
35 }
36
37 original_file_list_ := os.ls(test_path)!
38 dump(original_file_list_)
39 assert executable !in original_file_list_
40
41 assert os.execute('${os.quoted_path(vexe)} run .').output.trim_space() == 'Hello World!'
42 after_run_file_list := os.ls(test_path)!.filter(os.exists(it))
43 dump(after_run_file_list)
44 assert executable !in after_run_file_list
45
46 assert os.execute('${os.quoted_path(vexe)} .').exit_code == 0
47 assert os.execute('./${executable}').output.trim_space() == 'Hello World!'
48 after_compilation__ := os.ls(test_path)!
49 dump(after_compilation__)
50 assert executable in after_compilation__
51
52 assert os.execute('${os.quoted_path(vexe)} run .').output.trim_space() == 'Hello World!'
53 after_second_run___ := os.ls(test_path)!
54 dump(after_second_run___)
55 assert executable in after_second_run___
56}
57
58fn test_run_from_workdir_with_spaces() {
59 project_dir := os.join_path(test_path, 'issue 16501 path with spaces')
60 os.rmdir_all(project_dir) or {}
61 os.mkdir_all(project_dir)!
62 defer {
63 os.chdir(test_path) or {}
64 os.rmdir_all(project_dir) or {}
65 }
66 os.write_file(os.join_path(project_dir, 'main.v'),
67 'fn main(){\n\tprintln("Hello from a spaced path")\n}\n')!
68 os.write_file(os.join_path(project_dir, 'Test.vsh'),
69 "println('Hello from a spaced script path')\n")!
70 os.chdir(project_dir)!
71
72 run_file_res := os.execute('${os.quoted_path(vexe)} run Test.vsh')
73 assert run_file_res.exit_code == 0, run_file_res.output
74 assert run_file_res.output.trim_space() == 'Hello from a spaced script path'
75
76 run_dir_res := os.execute('${os.quoted_path(vexe)} run .')
77 assert run_dir_res.exit_code == 0, run_dir_res.output
78 assert run_dir_res.output.trim_space() == 'Hello from a spaced path'
79}
80
81fn test_existing_vsh_executable_uses_cache_until_source_is_newer() {
82 project_dir := os.join_path(test_path, 'existing vsh executable')
83 os.rmdir_all(project_dir) or {}
84 os.mkdir_all(project_dir)!
85 defer {
86 os.chdir(test_path) or {}
87 os.rmdir_all(project_dir) or {}
88 }
89 script_path := os.join_path(project_dir, 'script.vsh')
90 os.write_file(script_path, "println('Hello from cached vsh')\n")!
91 os.chdir(project_dir)!
92
93 cmd := '${os.quoted_path(vexe)} script.vsh'
94 first_res := os.execute(cmd)
95 assert first_res.exit_code == 0, first_res.output
96 assert first_res.output.trim_space() == 'Hello from cached vsh'
97
98 mut executable := os.join_path(project_dir, 'script')
99 $if windows {
100 executable += '.exe'
101 }
102 assert os.is_file(executable)
103
104 warm_cache_res := os.execute(cmd)
105 assert warm_cache_res.exit_code == 0, warm_cache_res.output
106 assert warm_cache_res.output.trim_space() == 'Hello from cached vsh'
107
108 cache_stamp := os.file_last_mod_unix(executable) + 3600
109 os.utime(executable, cache_stamp, cache_stamp)!
110
111 os.write_file(script_path, "println('Hello from rebuilt vsh')\n")!
112 os.utime(script_path, cache_stamp - 1, cache_stamp - 1)!
113 assert os.file_last_mod_unix(script_path) < cache_stamp
114
115 cached_res := os.execute(cmd)
116 assert cached_res.exit_code == 0, cached_res.output
117 assert cached_res.output.trim_space() == 'Hello from cached vsh'
118
119 os.utime(script_path, cache_stamp + 60, cache_stamp + 60)!
120 assert os.file_last_mod_unix(script_path) > cache_stamp
121
122 rebuilt_res := os.execute(cmd)
123 assert rebuilt_res.exit_code == 0, rebuilt_res.output
124 assert rebuilt_res.output.trim_space() == 'Hello from rebuilt vsh'
125}
126
127fn test_file_list() {
128 os.chdir(test_path)!
129 os.mkdir_all('filelist')!
130 os.write_file('filelist/main.v', 'module main
131fn main() {
132 part_a := AS{}
133 part_b := BS{}
134 println("\${part_a}=>\${part_b}")
135}')!
136 os.write_file('filelist/part_a.v', 'module main
137pub struct AS{}
138')!
139
140 os.write_file('filelist/part_b.v', 'module main
141pub struct BS{}
142')!
143
144 // `part_c.v` is not included in the compilation
145 // or there will be a conflit definition of `struct BS`
146 os.write_file('filelist/part_c.v', 'module main
147pub struct BS{}
148')!
149 mut executable := 'filelist_check'
150 $if windows {
151 executable += '.exe'
152 }
153
154 original_file_list_ := os.ls(test_path)!
155 dump(original_file_list_)
156 assert executable !in original_file_list_
157
158 cmd := '${os.quoted_path(vexe)} -o ${executable} filelist/main.v -file-list "filelist/part_a.v,filelist/part_b.v,"'
159 os.execute(cmd)
160 after_compile_file_list := os.ls(test_path)!.filter(os.exists(it))
161 dump(after_compile_file_list)
162 assert executable in after_compile_file_list
163 assert os.execute('./${executable}').output.trim_space() == 'AS{}=>BS{}'
164}
165
166fn test_run_custom_base_url_uses_project_root_lookup() {
167 os.chdir(test_path)!
168 project_dir := os.join_path(test_path, 'run_base_url_project')
169 defer {
170 os.chdir(test_path) or {}
171 }
172 os.mkdir_all(os.join_path(project_dir, 'source', 'foo'))!
173 os.mkdir_all(os.join_path(project_dir, 'source', 'modules', 'dep'))!
174 os.write_file(os.join_path(project_dir, 'v.mod'),
175 "Module {\n\tname: 'run_base_url_project'\n\tbase_url: 'source'\n\tdescription: ''\n\tversion: ''\n\tlicense: ''\n\tdependencies: []\n}\n")!
176 os.write_file(os.join_path(project_dir, 'source', 'main.v'), 'module main
177import foo
178import dep
179
180fn main() {
181 println(foo.name() + "+" + dep.name())
182}
183')!
184 os.write_file(os.join_path(project_dir, 'source', 'foo', 'foo.v'), 'module foo
185
186pub fn name() string {
187 return "foo"
188}
189')!
190 os.write_file(os.join_path(project_dir, 'source', 'modules', 'dep', 'dep.v'), 'module dep
191
192pub fn name() string {
193 return "dep"
194}
195')!
196 os.chdir(project_dir)!
197 assert run_v_ok('${os.quoted_path(vexe)} run .').trim_space() == 'foo+dep'
198 assert run_v_ok('${os.quoted_path(vexe)} run source').trim_space() == 'foo+dep'
199 assert run_v_ok('${os.quoted_path(vexe)} run ./source').trim_space() == 'foo+dep'
200}
201
202fn test_empty_local_dir_does_not_shadow_vlib_module() {
203 os.chdir(test_path)!
204 project_dir := os.join_path(test_path, 'run_empty_local_module_dir')
205 defer {
206 os.chdir(test_path) or {}
207 }
208 os.mkdir_all(os.join_path(project_dir, 'os'))!
209 os.write_file(os.join_path(project_dir, 'main.v'),
210 "module main\n\nimport os\n\nfn main() {\n\tprintln(os.is_dir('.'))\n}\n")!
211 os.chdir(project_dir)!
212
213 res := os.execute('${os.quoted_path(vexe)} run main.v')
214
215 assert res.exit_code == 0, res.output
216 assert res.output.trim_space() == 'true'
217}
218
219fn test_imports_with_same_resolved_path_are_parsed_once() {
220 os.chdir(test_path)!
221 workspace := os.join_path(test_path, 'run_duplicate_resolved_import_path')
222 project_dir := os.join_path(workspace, 'dupmod')
223 defer {
224 os.chdir(test_path) or {}
225 os.rmdir_all(workspace) or {}
226 }
227 os.mkdir_all(os.join_path(project_dir, 'providers'))!
228 os.write_file(os.join_path(project_dir, 'v.mod'), "Module {\n\tname: 'dupmod'\n}\n")!
229 os.write_file(os.join_path(project_dir, 'dupmod.v'), 'module dupmod
230
231import providers
232
233pub fn make_client() providers.Client {
234 return providers.Client{}
235}
236')!
237 os.write_file(os.join_path(project_dir, 'main_test.v'), 'module main
238
239import dupmod
240import dupmod.providers
241
242fn test_client() {
243 _ := dupmod.make_client()
244 _ := providers.Client{}
245}
246')!
247 os.write_file(os.join_path(project_dir, 'providers', 'client.v'), 'module providers
248
249pub struct Client {}
250')!
251 os.chdir(project_dir)!
252
253 res := os.execute('${os.quoted_path(vexe)} -check main_test.v')
254 assert res.exit_code == 0, res.output
255 assert !res.output.contains('cannot register struct')
256}
257
258fn test_duplicate_resolved_import_path_still_validates_module_name() {
259 $if windows {
260 return
261 }
262 os.chdir(test_path)!
263 workspace := os.join_path(test_path, 'run_duplicate_resolved_import_path_validation')
264 project_dir := os.join_path(workspace, 'project')
265 foo_dir := os.join_path(project_dir, 'modules', 'foo')
266 bar_dir := os.join_path(project_dir, 'modules', 'bar')
267 defer {
268 os.chdir(test_path) or {}
269 os.rmdir_all(workspace) or {}
270 }
271 os.mkdir_all(foo_dir)!
272 os.write_file(os.join_path(project_dir, 'v.mod'), "Module {\n\tname: 'project'\n}\n")!
273 os.write_file(os.join_path(foo_dir, 'foo.v'), 'module foo
274
275pub fn value() int {
276 return 1
277}
278')!
279 os.symlink(foo_dir, bar_dir) or { return }
280 os.write_file(os.join_path(project_dir, 'main.v'), 'module main
281
282import foo
283import bar
284
285fn main() {
286 println(foo.value())
287 println(bar.value())
288}
289')!
290 os.chdir(project_dir)!
291
292 res := os.execute('${os.quoted_path(vexe)} -check main.v')
293 assert res.exit_code != 0, res.output
294 assert res.output.contains('bad module definition'), res.output
295 assert res.output.contains('imports module "bar"'), res.output
296 assert res.output.contains('defined as module `foo`'), res.output
297}
298
299fn test_removed_src_layout_error_mentions_vmod_subdirs() {
300 os.chdir(test_path)!
301 project_dir := os.join_path(test_path, 'run_removed_src_project')
302 defer {
303 os.chdir(test_path) or {}
304 }
305 os.mkdir_all(os.join_path(project_dir, 'src'))!
306 os.write_file(os.join_path(project_dir, 'src', 'main.v'),
307 'fn main() {\n\tprintln("Hello from src")\n}\n')!
308 os.chdir(project_dir)!
309
310 res := os.execute('${os.quoted_path(vexe)} run .')
311 normalized_output := res.output.replace('\r\n', '\n')
312
313 assert res.exit_code != 0
314 assert normalized_output.contains('the virtual `src/` module directory is no longer supported.')
315 assert !normalized_output.contains('base_url')
316 assert normalized_output.contains('add `subdirs` to v.mod')
317 assert normalized_output.contains("subdirs: ['admin', 'repo', 'commit', 'ci', 'security', 'ssh', 'user']")
318}
319
320fn test_thirdparty_object_build_with_multiline_cflags() {
321 mut env := os.environ()
322 existing_cflags := if 'CFLAGS' in env { env['CFLAGS'] } else { '' }
323 env['CFLAGS'] = if existing_cflags == '' {
324 '-DTHIRDPARTY_MULTILINE_1=1\n-DTHIRDPARTY_MULTILINE_2=1'
325 } else {
326 '${existing_cflags}\n-DTHIRDPARTY_MULTILINE_1=1\n-DTHIRDPARTY_MULTILINE_2=1'
327 }
328 mut p := os.new_process(vexe)
329 p.set_work_folder(@VEXEROOT)
330 p.set_args(['run', os.join_path(@VEXEROOT, 'vlib', 'v', 'tests', 'project_with_c_code', 'main.v')])
331 p.set_environment(env)
332 p.set_redirect_stdio()
333 p.wait()
334 stdout := p.stdout_slurp()
335 stderr := p.stderr_slurp()
336 p.close()
337 assert p.code == 0, 'stdout:\n${stdout}\nstderr:\n${stderr}'
338}
339
340fn test_missing_library_is_reported_without_compiler_bug_hint() {
341 if os.user_os() == 'windows' {
342 return
343 }
344 os.chdir(test_path)!
345 os.mkdir_all('missing_library')!
346 lib_name := 'v_missing_lib_25499'
347 src_file := os.join_path('missing_library', 'main.v')
348 os.write_file(src_file, '#flag -l${lib_name}\nfn main() {}\n')!
349
350 res := os.execute('${os.quoted_path(vexe)} ${os.quoted_path(src_file)}')
351 normalized_output := res.output.replace('\r\n', '\n')
352
353 assert res.exit_code != 0
354 assert normalized_output.contains('builder error:')
355 assert normalized_output.contains('C library `${lib_name}` was not found while linking the generated program.'), normalized_output
356
357 assert normalized_output.contains('Please install the corresponding development package/libraries'), normalized_output
358
359 assert !normalized_output.contains('This is a V compiler bug'), normalized_output
360}
361
362fn test_windows_host_c_compiler_probe_is_skipped_for_non_windows_targets() {
363 assert builder.should_find_windows_host_c_compiler(&pref.Preferences{
364 backend: .c
365 os: .windows
366 })
367 assert !builder.should_find_windows_host_c_compiler(&pref.Preferences{
368 backend: .c
369 os: .linux
370 })
371 assert !builder.should_find_windows_host_c_compiler(&pref.Preferences{
372 backend: .c
373 os: .windows
374 output_cross_c: true
375 })
376 assert !builder.should_find_windows_host_c_compiler(&pref.Preferences{
377 backend: .js_browser
378 os: .windows
379 })
380}
381
382fn test_message_limit_notices_do_not_fail_build() {
383 os.chdir(test_path)!
384 src_file := os.join_path(test_path, 'message_limit_notices.v')
385 mut exe_file := os.join_path(test_path, 'message_limit_notices')
386 $if windows {
387 exe_file += '.exe'
388 }
389
390 mut lines := []string{}
391 for i in 0 .. 10 {
392 lines << '@[deprecated]'
393 lines << "@[deprecated_after: '3000-12-30']"
394 lines << 'fn future_${i}() {}'
395 }
396 lines << 'fn main() {'
397 for i in 0 .. 10 {
398 lines << '\tfuture_${i}()'
399 }
400 lines << '}'
401 os.write_file(src_file, lines.join('\n') + '\n')!
402
403 res :=
404 os.execute('${os.quoted_path(vexe)} -stats -message-limit 5 -o ${os.quoted_path(exe_file)} ${os.quoted_path(src_file)}')
405 normalized_output := res.output.replace('\r\n', '\n')
406
407 assert res.exit_code == 0, normalized_output
408 assert os.exists(exe_file), normalized_output
409 assert !normalized_output.contains('builder error: too many errors/warnings/notices')
410 assert normalized_output.contains('checker summary: 0 V errors, 0 V warnings, 10 V notices'), normalized_output
411}
412
413fn test_run_with_obscure_source_filenames() {
414 if os.user_os() == 'windows' {
415 return
416 }
417 obscure_dir := os.join_path(test_path, 'obscure_filenames')
418 os.rmdir_all(obscure_dir) or {}
419 os.mkdir_all(obscure_dir)!
420 source := "println('hi')\n"
421 for idx, file_name in [
422 "quote's.v",
423 '"hi".v',
424 "'.v",
425 '".v',
426 '.v',
427 '..v',
428 '...v',
429 '-.v',
430 '.c.v',
431 'line\nfeed.v',
432 ] {
433 src_file := os.join_path(obscure_dir, file_name)
434 os.write_file(src_file, source)!
435 out_file := os.join_path(obscure_dir, 'obscure_output_${idx}')
436 display_name := file_name.replace('\n', '\\n')
437 res :=
438 os.execute('${os.quoted_path(vexe)} -o ${os.quoted_path(out_file)} run ${os.quoted_path(src_file)}')
439 assert res.exit_code == 0, '${display_name}: ${res.output}'
440 assert res.output.trim_space() == 'hi', '${display_name}: ${res.output}'
441 assert os.read_file(src_file)! == source
442 os.rm(out_file) or {}
443 }
444}
445
446fn test_macos_2048_build_does_not_force_deployment_target() {
447 $if !macos {
448 return
449 }
450 game_path := os.join_path(@VEXEROOT, 'examples', '2048', '2048.v')
451 flags_output := run_v_ok('${os.quoted_path(vexe)} -dump-c-flags - ${os.quoted_path(game_path)}')
452 assert flags_output.contains('-fobjc-arc')
453 assert !flags_output.contains('-mmacosx-version-min=')
454}
455
456fn test_vlib_import_does_not_match_user_project_dir() {
457 // Regression for vlang/v#27151: when V is invoked from inside another
458 // project that happens to have a `src/sync/` directory (e.g. coreutils),
459 // a vlib file that imports `sync` (via the `shared` keyword in
460 // `v.ast.Table`) must still resolve to vlib's sync module, not to the
461 // user's foreign `main` files.
462 project_dir := os.join_path(test_path, 'foreign_sync_27151')
463 os.rmdir_all(project_dir) or {}
464 os.mkdir_all(os.join_path(project_dir, 'src', 'sync'))!
465 os.mkdir_all(os.join_path(project_dir, 'src', 'app'))!
466 defer {
467 os.chdir(test_path) or {}
468 os.rmdir_all(project_dir) or {}
469 }
470 os.write_file(os.join_path(project_dir, 'v.mod'),
471 "Module {\n\tname: 'foreign_sync_27151'\n\tversion: '0.0.1'\n\tdescription: ''\n\tlicense: 'MIT'\n\tdependencies: []\n}\n")!
472 // `src/sync/sync.v` has no `module` declaration, so V defaults it to
473 // `module main`. It must not be picked up as the `sync` module when
474 // a vlib file (here, `v.ast`, which uses `shared`) imports `sync`.
475 os.write_file(os.join_path(project_dir, 'src', 'sync', 'sync.v'),
476 'import os\n\nfn run() {\n\tprintln(os.args)\n}\n')!
477 os.write_file(os.join_path(project_dir, 'src', 'app', 'app_test.v'),
478 'import v.ast\n\nfn test_vast_table_loads() {\n\t_ := ast.new_table()\n}\n')!
479 os.chdir(project_dir)!
480
481 res := os.execute('${os.quoted_path(vexe)} -check src/app/app_test.v')
482 assert res.exit_code == 0, res.output
483 assert !res.output.contains('bad module definition'), res.output
484}
485
486fn test_vendored_sibling_modules_with_own_vmod_resolve_each_other() {
487 // Regression guard for the #27151 fix: when both the importer and the
488 // imported module live as siblings under `<project>/modules/` and each
489 // carry their own `v.mod`, the foreign-project check must still let them
490 // see one another. The importer's `v.mod` anchor needs to climb to the
491 // outermost project root, not stop at its own vendored package.
492 project_dir := os.join_path(test_path, 'vendored_sibling_27151')
493 os.rmdir_all(project_dir) or {}
494 os.mkdir_all(os.join_path(project_dir, 'modules', 'foo'))!
495 os.mkdir_all(os.join_path(project_dir, 'modules', 'bar'))!
496 defer {
497 os.chdir(test_path) or {}
498 os.rmdir_all(project_dir) or {}
499 }
500 os.write_file(os.join_path(project_dir, 'v.mod'),
501 "Module {\n\tname: 'vendored_sibling_27151'\n\tversion: '0.0.1'\n\tdescription: ''\n\tlicense: 'MIT'\n\tdependencies: []\n}\n")!
502 os.write_file(os.join_path(project_dir, 'modules', 'foo', 'v.mod'),
503 "Module {\n\tname: 'foo'\n\tversion: '0.0.1'\n\tdescription: ''\n\tlicense: 'MIT'\n\tdependencies: []\n}\n")!
504 os.write_file(os.join_path(project_dir, 'modules', 'bar', 'v.mod'),
505 "Module {\n\tname: 'bar'\n\tversion: '0.0.1'\n\tdescription: ''\n\tlicense: 'MIT'\n\tdependencies: []\n}\n")!
506 os.write_file(os.join_path(project_dir, 'modules', 'bar', 'bar.v'),
507 'module bar\n\npub fn name() string {\n\treturn "bar"\n}\n')!
508 os.write_file(os.join_path(project_dir, 'modules', 'foo', 'foo.v'),
509 'module foo\n\nimport bar\n\npub fn hello() string {\n\treturn bar.name()\n}\n')!
510 os.write_file(os.join_path(project_dir, 'main.v'),
511 'module main\n\nimport foo\n\nfn main() {\n\tprintln(foo.hello())\n}\n')!
512 os.chdir(project_dir)!
513
514 res := os.execute('${os.quoted_path(vexe)} -check .')
515 assert res.exit_code == 0, res.output
516 assert !res.output.contains('not found'), res.output
517}
518
519fn test_vendored_modules_dir_with_own_vmod_still_resolves() {
520 // Regression guard for the #27151 fix: a vendored dependency at
521 // `<project>/modules/<name>/` that carries its own `v.mod` must still
522 // resolve when imported from `<project>`'s own code. The foreign-project
523 // skip is path-based, so anything inside the importer's `v.mod` tree —
524 // including nested `v.mod`s under `modules/` — must remain visible.
525 project_dir := os.join_path(test_path, 'vendored_modules_27151')
526 os.rmdir_all(project_dir) or {}
527 os.mkdir_all(os.join_path(project_dir, 'modules', 'vendored'))!
528 defer {
529 os.chdir(test_path) or {}
530 os.rmdir_all(project_dir) or {}
531 }
532 os.write_file(os.join_path(project_dir, 'v.mod'),
533 "Module {\n\tname: 'vendored_modules_27151'\n\tversion: '0.0.1'\n\tdescription: ''\n\tlicense: 'MIT'\n\tdependencies: []\n}\n")!
534 os.write_file(os.join_path(project_dir, 'modules', 'vendored', 'v.mod'),
535 "Module {\n\tname: 'vendored'\n\tversion: '0.0.1'\n\tdescription: ''\n\tlicense: 'MIT'\n\tdependencies: []\n}\n")!
536 os.write_file(os.join_path(project_dir, 'modules', 'vendored', 'vendored.v'),
537 'module vendored\n\npub fn hello() string {\n\treturn "hi from vendored"\n}\n')!
538 os.write_file(os.join_path(project_dir, 'main.v'),
539 'module main\n\nimport vendored\n\nfn main() {\n\tprintln(vendored.hello())\n}\n')!
540 os.chdir(project_dir)!
541
542 res := os.execute('${os.quoted_path(vexe)} -check .')
543 assert res.exit_code == 0, res.output
544 assert !res.output.contains('not found'), res.output
545}
546
547fn test_macos_arch_flag_is_forwarded_to_c_compiler() {
548 $if !macos {
549 return
550 }
551 src_file := os.join_path(test_path, 'macos_arch_forwarding.v')
552 out_file := os.join_path(test_path, 'macos_arch_forwarding')
553 os.write_file(src_file, 'fn main() {\n\tprintln("hello")\n}\n')!
554 flags_output :=
555 run_v_ok('${os.quoted_path(vexe)} -cc clang -gc none -showcc -skip-running -arch amd64 -o ${os.quoted_path(out_file)} ${os.quoted_path(src_file)}')
556 assert flags_output.contains('-arch x86_64')
557}
558