xref: /netbsd-src/external/bsd/atf/dist/tools/atffile_test.cpp (revision ee43138c68eeee656501f419f250b4ed76e9d212)
1 //
2 // Automated Testing Framework (atf)
3 //
4 // Copyright (c) 2009 The NetBSD Foundation, Inc.
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 // 1. Redistributions of source code must retain the above copyright
11 //    notice, this list of conditions and the following disclaimer.
12 // 2. Redistributions in binary form must reproduce the above copyright
13 //    notice, this list of conditions and the following disclaimer in the
14 //    documentation and/or other materials provided with the distribution.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
17 // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 
30 extern "C" {
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 }
34 
35 #include <algorithm>
36 #include <fstream>
37 #include <memory>
38 
39 #include <atf-c++.hpp>
40 
41 #include "atffile.hpp"
42 #include "exceptions.hpp"
43 #include "test_helpers.hpp"
44 
45 namespace detail = tools::detail;
46 
47 // ------------------------------------------------------------------------
48 // Auxiliary functions.
49 // ------------------------------------------------------------------------
50 
51 namespace {
52 
53 typedef std::map< std::string, std::string > vars_map;
54 
55 static
56 std::unique_ptr< std::ofstream >
new_atffile(void)57 new_atffile(void)
58 {
59     std::unique_ptr< std::ofstream > os(new std::ofstream("Atffile"));
60     ATF_REQUIRE(*os);
61 
62     (*os) << "Content-Type: application/X-atf-atffile; version=\"1\"\n\n";
63     return os;
64 }
65 
66 static
67 void
touch_exec(const char * name)68 touch_exec(const char* name)
69 {
70     std::ofstream os(name);
71     ATF_REQUIRE(os);
72     os.close();
73     ATF_REQUIRE(::chmod(name, S_IRWXU) != -1);
74 }
75 
76 static inline
77 bool
is_in(const std::string & value,const std::vector<std::string> & v)78 is_in(const std::string& value, const std::vector< std::string >& v)
79 {
80     return std::find(v.begin(), v.end(), value) != v.end();
81 }
82 
83 } // anonymous namespace
84 
85 // ------------------------------------------------------------------------
86 // Tests cases for the "atffile" parser.
87 // ------------------------------------------------------------------------
88 
89 class atffile_reader : protected detail::atf_atffile_reader {
90     void
got_conf(const std::string & name,const std::string & val)91     got_conf(const std::string& name, const std::string& val)
92     {
93         m_calls.push_back("got_conf(" + name + ", " + val + ")");
94     }
95 
96     void
got_prop(const std::string & name,const std::string & val)97     got_prop(const std::string& name, const std::string& val)
98     {
99         m_calls.push_back("got_prop(" + name + ", " + val + ")");
100     }
101 
102     void
got_tp(const std::string & name,bool isglob)103     got_tp(const std::string& name, bool isglob)
104     {
105         m_calls.push_back("got_tp(" + name + ", " + (isglob ? "true" : "false")
106                   + ")");
107     }
108 
109     void
got_eof(void)110     got_eof(void)
111     {
112         m_calls.push_back("got_eof()");
113     }
114 
115 public:
atffile_reader(std::istream & is)116     atffile_reader(std::istream& is) :
117         detail::atf_atffile_reader(is)
118     {
119     }
120 
121     void
read(void)122     read(void)
123     {
124         atf_atffile_reader::read();
125     }
126 
127     std::vector< std::string > m_calls;
128 };
129 
130 ATF_TEST_CASE_WITHOUT_HEAD(atffile_1);
ATF_TEST_CASE_BODY(atffile_1)131 ATF_TEST_CASE_BODY(atffile_1)
132 {
133     const char* input =
134         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
135         "\n"
136     ;
137 
138     const char* exp_calls[] = {
139         "got_eof()",
140         NULL
141     };
142 
143     const char* exp_errors[] = {
144         NULL
145     };
146 
147     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
148 }
149 
150 ATF_TEST_CASE_WITHOUT_HEAD(atffile_2);
ATF_TEST_CASE_BODY(atffile_2)151 ATF_TEST_CASE_BODY(atffile_2)
152 {
153     const char* input =
154         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
155         "\n"
156         "# This is a comment on a line of its own.\n"
157         "# And this is another one.\n"
158         "\n"
159         "	    # Another after some whitespace.\n"
160         "\n"
161         "# The last one after an empty line.\n"
162     ;
163 
164     const char* exp_calls[] = {
165         "got_eof()",
166         NULL
167     };
168 
169     const char* exp_errors[] = {
170         NULL
171     };
172 
173     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
174 }
175 
176 ATF_TEST_CASE_WITHOUT_HEAD(atffile_3);
ATF_TEST_CASE_BODY(atffile_3)177 ATF_TEST_CASE_BODY(atffile_3)
178 {
179     const char* input =
180         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
181         "\n"
182         "conf: var1=value1\n"
183         "conf: var2 = value2\n"
184         "conf: var3	=	value3\n"
185         "conf: var4	    =	    value4\n"
186         "\n"
187         "conf:var5=value5\n"
188         "    conf:var6=value6\n"
189         "\n"
190         "conf: var7 = \"This is a long value.\"\n"
191         "conf: var8 = \"Single-word\"\n"
192         "conf: var9 = \"    Single-word	\"\n"
193         "conf: var10 = Single-word\n"
194     ;
195 
196     const char* exp_calls[] = {
197         "got_conf(var1, value1)",
198         "got_conf(var2, value2)",
199         "got_conf(var3, value3)",
200         "got_conf(var4, value4)",
201         "got_conf(var5, value5)",
202         "got_conf(var6, value6)",
203         "got_conf(var7, This is a long value.)",
204         "got_conf(var8, Single-word)",
205         "got_conf(var9,     Single-word	)",
206         "got_conf(var10, Single-word)",
207         "got_eof()",
208         NULL
209     };
210 
211     const char* exp_errors[] = {
212         NULL
213     };
214 
215     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
216 }
217 
218 ATF_TEST_CASE_WITHOUT_HEAD(atffile_4);
ATF_TEST_CASE_BODY(atffile_4)219 ATF_TEST_CASE_BODY(atffile_4)
220 {
221     const char* input =
222         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
223         "\n"
224         "prop: var1=value1\n"
225         "prop: var2 = value2\n"
226         "prop: var3	=	value3\n"
227         "prop: var4	    =	    value4\n"
228         "\n"
229         "prop:var5=value5\n"
230         "    prop:var6=value6\n"
231         "\n"
232         "prop: var7 = \"This is a long value.\"\n"
233         "prop: var8 = \"Single-word\"\n"
234         "prop: var9 = \"    Single-word	\"\n"
235         "prop: var10 = Single-word\n"
236     ;
237 
238     const char* exp_calls[] = {
239         "got_prop(var1, value1)",
240         "got_prop(var2, value2)",
241         "got_prop(var3, value3)",
242         "got_prop(var4, value4)",
243         "got_prop(var5, value5)",
244         "got_prop(var6, value6)",
245         "got_prop(var7, This is a long value.)",
246         "got_prop(var8, Single-word)",
247         "got_prop(var9,     Single-word	)",
248         "got_prop(var10, Single-word)",
249         "got_eof()",
250         NULL
251     };
252 
253     const char* exp_errors[] = {
254         NULL
255     };
256 
257     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
258 }
259 
260 ATF_TEST_CASE_WITHOUT_HEAD(atffile_5);
ATF_TEST_CASE_BODY(atffile_5)261 ATF_TEST_CASE_BODY(atffile_5)
262 {
263     const char* input =
264         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
265         "\n"
266         "tp:foo\n"
267         "tp: foo\n"
268         "tp:  foo\n"
269         "tp:	foo\n"
270         "tp:	    foo\n"
271         "tp: \"name with spaces\"\n"
272         "tp: \"single-word\"\n"
273         "tp: single-word\n"
274         "\n"
275         "tp-glob:foo*?bar\n"
276         "tp-glob: foo*?bar\n"
277         "tp-glob:  foo*?bar\n"
278         "tp-glob:	foo*?bar\n"
279         "tp-glob:	    foo*?bar\n"
280         "tp-glob: \"glob * with ? spaces\"\n"
281         "tp-glob: \"single-*-word\"\n"
282         "tp-glob: single-*-word\n"
283     ;
284 
285     const char* exp_calls[] = {
286         "got_tp(foo, false)",
287         "got_tp(foo, false)",
288         "got_tp(foo, false)",
289         "got_tp(foo, false)",
290         "got_tp(foo, false)",
291         "got_tp(name with spaces, false)",
292         "got_tp(single-word, false)",
293         "got_tp(single-word, false)",
294         "got_tp(foo*?bar, true)",
295         "got_tp(foo*?bar, true)",
296         "got_tp(foo*?bar, true)",
297         "got_tp(foo*?bar, true)",
298         "got_tp(foo*?bar, true)",
299         "got_tp(glob * with ? spaces, true)",
300         "got_tp(single-*-word, true)",
301         "got_tp(single-*-word, true)",
302         "got_eof()",
303         NULL
304     };
305 
306     const char* exp_errors[] = {
307         NULL
308     };
309 
310     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
311 }
312 
313 ATF_TEST_CASE_WITHOUT_HEAD(atffile_6);
ATF_TEST_CASE_BODY(atffile_6)314 ATF_TEST_CASE_BODY(atffile_6)
315 {
316     const char* input =
317         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
318         "\n"
319         "prop: foo = bar # A comment.\n"
320         "conf: foo = bar # A comment.\n"
321         "tp: foo # A comment.\n"
322         "tp-glob: foo # A comment.\n"
323     ;
324 
325     const char* exp_calls[] = {
326         "got_prop(foo, bar)",
327         "got_conf(foo, bar)",
328         "got_tp(foo, false)",
329         "got_tp(foo, true)",
330         "got_eof()",
331         NULL
332     };
333 
334     const char* exp_errors[] = {
335         NULL
336     };
337 
338     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
339 }
340 
341 ATF_TEST_CASE_WITHOUT_HEAD(atffile_50);
ATF_TEST_CASE_BODY(atffile_50)342 ATF_TEST_CASE_BODY(atffile_50)
343 {
344     const char* input =
345         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
346         "\n"
347         "foo\n"
348     ;
349 
350     const char* exp_calls[] = {
351         NULL
352     };
353 
354     // NO_CHECK_STYLE_BEGIN
355     const char* exp_errors[] = {
356         "3: Unexpected token `foo'; expected conf, #, prop, tp, tp-glob, a new line or eof",
357         NULL
358     };
359     // NO_CHECK_STYLE_END
360 
361     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
362 }
363 
364 ATF_TEST_CASE_WITHOUT_HEAD(atffile_51);
ATF_TEST_CASE_BODY(atffile_51)365 ATF_TEST_CASE_BODY(atffile_51)
366 {
367     const char* input =
368         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
369         "\n"
370         "foo bar\n"
371         "baz\n"
372     ;
373 
374     const char* exp_calls[] = {
375         NULL
376     };
377 
378     // NO_CHECK_STYLE_BEGIN
379     const char* exp_errors[] = {
380         "3: Unexpected token `foo'; expected conf, #, prop, tp, tp-glob, a new line or eof",
381         "4: Unexpected token `baz'; expected conf, #, prop, tp, tp-glob, a new line or eof",
382         NULL
383     };
384     // NO_CHECK_STYLE_END
385 
386     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
387 }
388 
389 ATF_TEST_CASE_WITHOUT_HEAD(atffile_52);
ATF_TEST_CASE_BODY(atffile_52)390 ATF_TEST_CASE_BODY(atffile_52)
391 {
392     const char* input =
393         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
394         "\n"
395         "conf\n"
396         "conf:\n"
397         "conf: foo =\n"
398         "conf: bar = # A comment.\n"
399         "\n"
400         "prop\n"
401         "prop:\n"
402         "prop: foo =\n"
403         "prop: bar = # A comment.\n"
404         "\n"
405         "tp\n"
406         "tp:\n"
407         "tp: # A comment.\n"
408         "\n"
409         "tp-glob\n"
410         "tp-glob:\n"
411         "tp-glob: # A comment.\n"
412     ;
413 
414     const char* exp_calls[] = {
415         NULL
416     };
417 
418     const char* exp_errors[] = {
419         "3: Unexpected token `<<NEWLINE>>'; expected `:'",
420         "4: Unexpected token `<<NEWLINE>>'; expected variable name",
421         "5: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
422         "6: Unexpected token `#'; expected word or quoted string",
423         "8: Unexpected token `<<NEWLINE>>'; expected `:'",
424         "9: Unexpected token `<<NEWLINE>>'; expected property name",
425         "10: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
426         "11: Unexpected token `#'; expected word or quoted string",
427         "13: Unexpected token `<<NEWLINE>>'; expected `:'",
428         "14: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
429         "15: Unexpected token `#'; expected word or quoted string",
430         "17: Unexpected token `<<NEWLINE>>'; expected `:'",
431         "18: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
432         "19: Unexpected token `#'; expected word or quoted string",
433         NULL
434     };
435 
436     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
437 }
438 
439 ATF_TEST_CASE_WITHOUT_HEAD(atffile_53);
ATF_TEST_CASE_BODY(atffile_53)440 ATF_TEST_CASE_BODY(atffile_53)
441 {
442     const char* input =
443         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
444         "\n"
445         "prop: foo = \"Correct value\" # With comment.\n"
446         "\n"
447         "prop: bar = # A comment.\n"
448         "\n"
449         "prop: baz = \"Last variable\"\n"
450         "\n"
451         "# End of file.\n"
452     ;
453 
454     const char* exp_calls[] = {
455         "got_prop(foo, Correct value)",
456         NULL
457     };
458 
459     const char* exp_errors[] = {
460         "5: Unexpected token `#'; expected word or quoted string",
461         NULL
462     };
463 
464     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
465 }
466 
467 ATF_TEST_CASE_WITHOUT_HEAD(atffile_54);
ATF_TEST_CASE_BODY(atffile_54)468 ATF_TEST_CASE_BODY(atffile_54)
469 {
470     const char* input =
471         "Content-Type: application/X-atf-atffile; version=\"1\"\n"
472         "\n"
473         "prop: foo = \"\n"
474         "prop: bar = \"text\n"
475         "prop: baz = \"te\\\"xt\n"
476         "prop: last = \"\\\"\n"
477     ;
478 
479     const char* exp_calls[] = {
480         NULL
481     };
482 
483     const char* exp_errors[] = {
484         "3: Missing double quotes before end of line",
485         "4: Missing double quotes before end of line",
486         "5: Missing double quotes before end of line",
487         "6: Missing double quotes before end of line",
488         NULL
489     };
490 
491     do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
492 }
493 
494 // ------------------------------------------------------------------------
495 // Tests cases for the "atffile" class.
496 // ------------------------------------------------------------------------
497 
498 ATF_TEST_CASE(atffile_getters);
ATF_TEST_CASE_HEAD(atffile_getters)499 ATF_TEST_CASE_HEAD(atffile_getters) {}
ATF_TEST_CASE_BODY(atffile_getters)500 ATF_TEST_CASE_BODY(atffile_getters) {
501     vars_map config_vars;
502     config_vars["config-var-1"] = "value 1";
503 
504     std::vector< std::string > test_program_names;
505     test_program_names.push_back("test-program-1");
506 
507     vars_map properties;
508     properties["test-suite"] = "a test name";
509 
510     const tools::atffile atffile(config_vars, test_program_names, properties);
511     ATF_REQUIRE(config_vars == atffile.conf());
512     ATF_REQUIRE(test_program_names == atffile.tps());
513     ATF_REQUIRE(properties == atffile.props());
514 }
515 
516 // ------------------------------------------------------------------------
517 // Tests cases for the free functions.
518 // ------------------------------------------------------------------------
519 
520 ATF_TEST_CASE_WITHOUT_HEAD(read_ok_simple);
ATF_TEST_CASE_BODY(read_ok_simple)521 ATF_TEST_CASE_BODY(read_ok_simple) {
522     std::unique_ptr< std::ofstream > os = new_atffile();
523     (*os) << "prop: test-suite = foo\n";
524     (*os) << "tp: tp-1\n";
525     (*os) << "conf: var1 = value1\n";
526     (*os) << "tp: tp-2\n";
527     (*os) << "tp: tp-3\n";
528     (*os) << "prop: prop1 = propvalue1\n";
529     (*os) << "conf: var2 = value2\n";
530     (*os).close();
531 
532     touch_exec("tp-1");
533     touch_exec("tp-2");
534     touch_exec("tp-3");
535 
536     const tools::atffile atffile = tools::read_atffile(
537         tools::fs::path("Atffile"));
538     ATF_REQUIRE_EQ(2, atffile.conf().size());
539     ATF_REQUIRE_EQ("value1", atffile.conf().find("var1")->second);
540     ATF_REQUIRE_EQ("value2", atffile.conf().find("var2")->second);
541     ATF_REQUIRE_EQ(3, atffile.tps().size());
542     ATF_REQUIRE(is_in("tp-1", atffile.tps()));
543     ATF_REQUIRE(is_in("tp-2", atffile.tps()));
544     ATF_REQUIRE(is_in("tp-3", atffile.tps()));
545     ATF_REQUIRE_EQ(2, atffile.props().size());
546     ATF_REQUIRE_EQ("foo", atffile.props().find("test-suite")->second);
547     ATF_REQUIRE_EQ("propvalue1", atffile.props().find("prop1")->second);
548 }
549 
550 ATF_TEST_CASE_WITHOUT_HEAD(read_ok_some_globs);
ATF_TEST_CASE_BODY(read_ok_some_globs)551 ATF_TEST_CASE_BODY(read_ok_some_globs) {
552     std::unique_ptr< std::ofstream > os = new_atffile();
553     (*os) << "prop: test-suite = foo\n";
554     (*os) << "tp: foo\n";
555     (*os) << "tp-glob: *K*\n";
556     (*os) << "tp: bar\n";
557     (*os) << "tp-glob: t_*\n";
558     (*os).close();
559 
560     touch_exec("foo");
561     touch_exec("bar");
562     touch_exec("aK");
563     touch_exec("KKKKK");
564     touch_exec("t_hello");
565     touch_exec("zzzt_hello");
566 
567     const tools::atffile atffile = tools::read_atffile(
568         tools::fs::path("Atffile"));
569     ATF_REQUIRE_EQ(5, atffile.tps().size());
570     ATF_REQUIRE(is_in("foo", atffile.tps()));
571     ATF_REQUIRE(is_in("bar", atffile.tps()));
572     ATF_REQUIRE(is_in("aK", atffile.tps()));
573     ATF_REQUIRE(is_in("KKKKK", atffile.tps()));
574     ATF_REQUIRE(is_in("t_hello", atffile.tps()));
575 }
576 
577 ATF_TEST_CASE_WITHOUT_HEAD(read_missing_test_suite);
ATF_TEST_CASE_BODY(read_missing_test_suite)578 ATF_TEST_CASE_BODY(read_missing_test_suite) {
579     std::unique_ptr< std::ofstream > os = new_atffile();
580     (*os).close();
581 
582     try {
583         (void)tools::read_atffile(tools::fs::path("Atffile"));
584         ATF_FAIL("Missing property 'test-suite' did not raise an error");
585     } catch (const tools::not_found_error< std::string >& e) {
586         ATF_REQUIRE_EQ("test-suite", e.get_value());
587     }
588 }
589 
590 ATF_TEST_CASE_WITHOUT_HEAD(read_missing_test_program);
ATF_TEST_CASE_BODY(read_missing_test_program)591 ATF_TEST_CASE_BODY(read_missing_test_program) {
592     std::unique_ptr< std::ofstream > os = new_atffile();
593     (*os) << "tp: foo\n";
594     (*os) << "tp: bar\n";
595     (*os) << "tp: baz\n";
596     (*os).close();
597 
598     touch_exec("foo");
599     touch_exec("baz");
600 
601     try {
602         (void)tools::read_atffile(tools::fs::path("Atffile"));
603         ATF_FAIL("Missing file 'bar' did not raise an error");
604     } catch (const tools::not_found_error< tools::fs::path >& e) {
605         ATF_REQUIRE_EQ("bar", e.get_value().str());
606     }
607 }
608 
609 // ------------------------------------------------------------------------
610 // Main.
611 // ------------------------------------------------------------------------
612 
ATF_INIT_TEST_CASES(tcs)613 ATF_INIT_TEST_CASES(tcs)
614 {
615     // Add the test cases for the parser class.
616     ATF_ADD_TEST_CASE(tcs, atffile_1);
617     ATF_ADD_TEST_CASE(tcs, atffile_2);
618     ATF_ADD_TEST_CASE(tcs, atffile_3);
619     ATF_ADD_TEST_CASE(tcs, atffile_4);
620     ATF_ADD_TEST_CASE(tcs, atffile_5);
621     ATF_ADD_TEST_CASE(tcs, atffile_6);
622     ATF_ADD_TEST_CASE(tcs, atffile_50);
623     ATF_ADD_TEST_CASE(tcs, atffile_51);
624     ATF_ADD_TEST_CASE(tcs, atffile_52);
625     ATF_ADD_TEST_CASE(tcs, atffile_53);
626     ATF_ADD_TEST_CASE(tcs, atffile_54);
627 
628     // Add the test cases for the atffile class.
629     ATF_ADD_TEST_CASE(tcs, atffile_getters);
630 
631     // Add the test cases for the free functions.
632     ATF_ADD_TEST_CASE(tcs, read_ok_simple);
633     ATF_ADD_TEST_CASE(tcs, read_ok_some_globs);
634     ATF_ADD_TEST_CASE(tcs, read_missing_test_suite);
635     ATF_ADD_TEST_CASE(tcs, read_missing_test_program);
636 }
637