This repository has been archived by the owner on Feb 11, 2021. It is now read-only.
/
runner.c
776 lines (626 loc) · 20.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
Copyright (C) 2011 Markus Kauppila <markus.kauppila@gmail.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
21
#include "SDL/SDL.h"
22
23
24
25
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
26
#include <string.h>
27
#include <dirent.h>
28
29
30
#include <sys/types.h>
31
#include "SDL_test.h"
32
#include "logger.h"
33
34
//!< Function pointer to a test case function
35
typedef void (*TestCaseFp)(void *arg);
36
//!< Function pointer to a test case init function
37
typedef void (*TestCaseInitFp)(const int);
38
//!< Function pointer to a test case quit function
39
40
typedef int (*TestCaseQuitFp)(void);
41
42
43
//!< Flag for executing tests in-process
static int execute_inproc = 0;
44
45
//!< Flag for only printing out the test names
static int only_print_tests = 0;
46
47
48
49
//!< Flag for executing only test with selected name
static int only_selected_test = 0;
//!< Flag for executing only the selected test suite
static int only_selected_suite = 0;
50
51
//!< Flag for executing only tests that contain certain string in their name
static int only_tests_with_string = 0;
52
53
//!< Flag for enabling XML logging
static int xml_enabled = 0;
54
55
//! Flag for enabling user-supplied style sheet for XML test report
static int custom_xsl_enabled = 0;
56
57
58
//!< Size of the test and suite name buffers
59
#define NAME_BUFFER_SIZE 1024
60
61
62
63
//!< Name of the selected test
char selected_test_name[NAME_BUFFER_SIZE];
//!< Name of the selected suite
char selected_suite_name[NAME_BUFFER_SIZE];
64
65
66
67
//!< substring of test case name
char testcase_name_substring[NAME_BUFFER_SIZE];
68
69
70
//! Name for user-supplied XSL style sheet name
char xsl_stylesheet_name[NAME_BUFFER_SIZE];
71
72
73
//! Default directory of the test suites
#define DEFAULT_TEST_DIRECTORY "tests/"
74
75
/*!
76
77
* Holds information about test suite such as it's name
* and pointer to dynamic library. Implemented as linked list.
78
79
*/
typedef struct TestSuiteReference {
80
char *name; //!< test suite name
81
char *directoryPath; //!< test suites path (eg. tests/libtestsuite)
82
void *library; //!< pointer to shared/dynamic library implementing the suite
83
84
85
86
struct TestSuiteReference *next; //!< Pointer to next item in the list
} TestSuiteReference;
87
88
89
90
91
92
/*!
* Holds information about the tests that will be executed.
*
* Implemented as linked list.
*/
93
94
95
96
typedef struct TestCaseItem {
char *testName;
char *suiteName;
97
98
99
100
char *description;
long requirements;
long timeout;
101
102
103
TestCaseInitFp testCaseInit;
TestCaseFp testCase;
TestCaseQuitFp testCaseQuit;
104
105
struct TestCaseItem *next;
106
107
108
109
110
111
112
113
114
115
} TestCase;
/*! Some function prototypes. Add the rest of functions and move to runner.h */
TestCaseFp LoadTestCaseFunction(void *suite, char *testName);
TestCaseInitFp LoadTestCaseInitFunction(void *suite);
TestCaseQuitFp LoadTestCaseQuitFunction(void *suite);
TestCaseReference **QueryTestCaseReferences(void *library);
116
117
118
119
120
121
122
/*!
* Goes through the previously loaded test suites and
* loads test cases from them. Test cases are filtered
* during the process. Function will only return the
* test cases which aren't filtered out.
*
123
* \param suites previously loaded test suites
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
*
* \return Test cases that survived filtering process.
*/
TestCase *
LoadTestCases(TestSuiteReference *suites)
{
TestCase *testCases = NULL;
TestSuiteReference *suiteReference = NULL;
for(suiteReference = suites; suiteReference; suiteReference = suiteReference->next) {
TestCaseReference **tests = QueryTestCaseReferences(suiteReference->library);
TestCaseReference *testReference = NULL;
int counter = 0;
for(testReference = tests[counter]; testReference; testReference = tests[++counter]) {
void *suite = suiteReference->library;
// Load test case functions
TestCaseInitFp testCaseInit = LoadTestCaseInitFunction(suiteReference->library);
TestCaseQuitFp testCaseQuit = LoadTestCaseQuitFunction(suiteReference->library);
TestCaseFp testCase = (TestCaseFp) LoadTestCaseFunction(suiteReference->library, testReference->name);
// Do the filtering
if(FilterTestCase(testReference)) {
TestCase *item = SDL_malloc(sizeof(TestCase));
memset(item, 0, sizeof(TestCase));
item->testCaseInit = testCaseInit;
item->testCase = testCase;
item->testCaseQuit = testCaseQuit;
156
// copy suite name
157
int length = SDL_strlen(suiteReference->name) + 1;
158
item->suiteName = SDL_malloc(length);
159
strncpy(item->suiteName, suiteReference->name, length);
160
161
// copy test name
162
length = SDL_strlen(testReference->name) + 1;
163
item->testName = SDL_malloc(length);
164
strncpy(item->testName, testReference->name, length);
165
166
// copy test description
167
length = SDL_strlen(testReference->description) + 1;
168
item->description = SDL_malloc(length);
169
strncpy(item->description, testReference->description, length);
170
171
172
173
item->requirements = testReference->requirements;
item->timeout = testReference->timeout;
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// prepend the list
item->next = testCases;
testCases = item;
//printf("Added test: %s\n", testReference->name);
}
}
}
return testCases;
}
/*!
* Unloads the given TestCases. Frees all the resources
* allocated for test cases.
*
* \param testCases Test cases to be deallocated
*/
void
UnloadTestCases(TestCase *testCases)
{
TestCase *ref = testCases;
while(ref) {
SDL_free(ref->testName);
SDL_free(ref->suiteName);
200
SDL_free(ref->description);
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
TestCase *temp = ref->next;
SDL_free(ref);
ref = temp;
}
testCases = NULL;
}
/*!
* Filters a test case based on its properties in TestCaseReference and user
* preference.
*
* \return Non-zero means test will be added to execution list, zero means opposite
*/
int
FilterTestCase(TestCaseReference *testReference)
{
int retVal = 1;
if(testReference->enabled == TEST_DISABLED) {
retVal = 0;
}
226
227
228
229
230
231
232
233
if(only_selected_test) {
if(SDL_strncmp(testReference->name, selected_test_name, NAME_BUFFER_SIZE) == 0) {
retVal = 1;
} else {
retVal = 0;
}
}
234
235
236
237
238
239
240
241
242
243
244
245
if(only_tests_with_string) {
if(strstr(testReference->name, testcase_name_substring) != NULL) {
retVal = 1;
} else {
retVal = 0;
}
}
return retVal;
}
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
/*!
* Scans the tests/ directory and returns the names
* of the dynamic libraries implementing the test suites.
*
* Note: currently function assumes that test suites names
* are in following format: libtestsuite.dylib or libtestsuite.so.
*
* Note: if only_selected_suite flags is non-zero, only the selected
* test will be loaded.
*
* \param directoryName Name of the directory which will be scanned
* \param extension What file extension is used with dynamic objects
*
* \return Pointer to TestSuiteReference which holds all the info about suites
*/
TestSuiteReference *
ScanForTestSuites(char *directoryName, char *extension)
{
typedef struct dirent Entry;
DIR *directory = opendir(directoryName);
TestSuiteReference *suites = NULL;
Entry *entry = NULL;
if(!directory) {
perror("Couldn't open test suite directory!");
}
while(entry = readdir(directory)) {
if(strlen(entry->d_name) > 2) { // discards . and ..
const char *delimiters = ".";
char *name = strtok(entry->d_name, delimiters);
char *ext = strtok(NULL, delimiters);
// filter out all other suites but the selected test suite
int ok = 1;
if(only_selected_suite) {
ok = SDL_strncmp(selected_suite_name, name, NAME_BUFFER_SIZE) == 0;
}
if(ok && SDL_strcmp(ext, extension) == 0) {
// create test suite reference
TestSuiteReference *reference = (TestSuiteReference *) SDL_malloc(sizeof(TestSuiteReference));
memset(reference, 0, sizeof(TestSuiteReference));
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
const int dirSize = SDL_strlen(directoryName);
const int extSize = SDL_strlen(ext);
const int nameSize = SDL_strlen(name) + 1;
// copy the name
reference->name = SDL_malloc(nameSize * sizeof(char));
if(reference->name == NULL) {
SDL_free(reference);
return NULL;
}
SDL_snprintf(reference->name, nameSize, "%s", name);
// copy the directory path
const int dpSize = dirSize + nameSize + 1 + extSize + 1;
reference->directoryPath = SDL_malloc(dpSize * sizeof(char));
if(reference->directoryPath == NULL) {
SDL_free(reference->name);
SDL_free(reference);
return NULL;
}
SDL_snprintf(reference->directoryPath, dpSize, "%s%s.%s",
directoryName, name, ext);
315
316
317
318
319
320
321
322
323
324
325
326
327
reference->next = suites;
suites = reference;
}
}
}
closedir(directory);
return suites;
}
328
329
330
/*!
* Loads test suite which is implemented as dynamic library.
*
331
* \param testSuiteName Name of the test suite which will be loaded
332
333
334
335
*
* \return Pointer to loaded test suite, or NULL if library could not be loaded
*/
void *
336
LoadTestSuite(const TestSuiteReference *suite)
337
{
338
void *library = SDL_LoadObject(suite->directoryPath);
339
if(library == NULL) {
340
fprintf(stderr, "Loading %s failed\n", suite->name);
341
fprintf(stderr, "%s\n", SDL_GetError());
342
343
}
344
345
346
return library;
}
347
348
349
350
351
352
353
354
355
356
357
/*!
* Goes through all the given TestSuiteReferences
* and loads the dynamic libraries. Updates the suites
* parameter on-the-fly and returns it.
*
* \param suites Suites that will be loaded
*
* \return Updated TestSuiteReferences with pointer to loaded libraries
*/
TestSuiteReference *
358
LoadTestSuites(TestSuiteReference *suites)
359
360
361
{
TestSuiteReference *reference = NULL;
for(reference = suites; reference; reference = reference->next) {
362
reference->library = LoadTestSuite(reference);
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
}
return suites;
}
/*!
* Unloads the given TestSuiteReferences. Frees all
* the allocated resources including the dynamic libraries.
*
* \param suites TestSuiteReferences for deallocation process
*/
void
UnloadTestSuites(TestSuiteReference *suites)
{
TestSuiteReference *ref = suites;
while(ref) {
SDL_free(ref->name);
381
SDL_free(ref->directoryPath);
382
383
384
385
386
387
388
389
390
391
392
SDL_UnloadObject(ref->library);
TestSuiteReference *temp = ref->next;
SDL_free(ref);
ref = temp;
}
suites = NULL;
}
393
394
395
396
/*!
* Loads the test case references from the given test suite.
* \param library Previously loaded dynamic library AKA test suite
397
* \return Pointer to array of TestCaseReferences or NULL if function failed
398
*/
399
TestCaseReference **
400
QueryTestCaseReferences(void *library)
401
{
402
TestCaseReference **(*suite)(void);
403
404
405
406
407
408
suite = (TestCaseReference **(*)(void)) SDL_LoadFunction(library, "QueryTestSuite");
if(suite == NULL) {
fprintf(stderr, "Loading QueryTestCaseReferences() failed.\n");
fprintf(stderr, "%s\n", SDL_GetError());
}
409
410
411
412
413
414
TestCaseReference **tests = suite();
if(tests == NULL) {
fprintf(stderr, "Failed to load test references.\n");
fprintf(stderr, "%s\n", SDL_GetError());
}
415
416
return tests;
417
418
}
419
420
421
422
/*!
* Loads test case from a test suite
*
423
424
* \param suite a test suite
* \param testName Name of the test that is going to be loaded
425
*
426
* \return Function Pointer (TestCase) to loaded test case, NULL if function failed
427
*/
428
429
TestCaseFp
LoadTestCaseFunction(void *suite, char *testName)
430
{
431
TestCaseFp test = (TestCaseFp) SDL_LoadFunction(suite, testName);
432
if(test == NULL) {
433
434
fprintf(stderr, "Loading test failed, tests == NULL\n");
fprintf(stderr, "%s\n", SDL_GetError());
435
436
437
438
439
}
return test;
}
440
441
442
443
444
445
446
447
448
/*!
* Loads function that initialises the test case from the
* given test suite.
*
* \param suite Used test suite
*
* \return Function pointer (TestCaseInit) which points to loaded init function. NULL if function fails.
*/
449
450
451
TestCaseInitFp
LoadTestCaseInitFunction(void *suite) {
TestCaseInitFp testCaseInit = (TestCaseInitFp) SDL_LoadFunction(suite, "_TestCaseInit");
452
453
454
455
456
457
458
459
if(testCaseInit == NULL) {
fprintf(stderr, "Loading TestCaseInit function failed, testCaseInit == NULL\n");
fprintf(stderr, "%s\n", SDL_GetError());
}
return testCaseInit;
}
460
461
462
463
464
465
466
467
468
/*!
* Loads function that deinitialises the executed test case from the
* given test suite.
*
* \param suite Used test suite
*
* \return Function pointer (TestCaseInit) which points to loaded init function. NULL if function fails.
*/
469
470
471
TestCaseQuitFp
LoadTestCaseQuitFunction(void *suite) {
TestCaseQuitFp testCaseQuit = (TestCaseQuitFp) SDL_LoadFunction(suite, "_TestCaseQuit");
472
473
474
475
476
477
478
if(testCaseQuit == NULL) {
fprintf(stderr, "Loading TestCaseQuit function failed, testCaseQuit == NULL\n");
fprintf(stderr, "%s\n", SDL_GetError());
}
return testCaseQuit;
}
479
480
481
/*!
482
483
484
485
* If using out-of-proc execution of tests. This function
* will handle the return value of the child process
* and interprets it to the runner. Also prints warnings
* if child was aborted by a signela.
486
*
487
* \param stat_lock information about the exited child process
488
*
489
* \return 0 if test case succeeded, 1 otherwise
490
*/
491
int
492
HandleChildProcessReturnValue(int stat_lock)
493
{
494
int returnValue = -1;
495
496
497
if(WIFEXITED(stat_lock)) {
returnValue = WEXITSTATUS(stat_lock);
498
499
} else if(WIFSIGNALED(stat_lock)) {
int signal = WTERMSIG(stat_lock);
500
fprintf(stderr, "FAILURE: test was aborted due to signal no %d\n", signal);
501
returnValue = 1;
502
503
}
504
return returnValue;
505
506
}
507
508
509
510
511
/*!
* Executes a test case. Loads the test, executes it and
* returns the tests return value to the caller.
*
512
* \param testItem The test case that will be executed
513
514
515
* \return The return value of the test. Zero means success, non-zero failure.
*/
int
516
ExecuteTest(TestCase *testItem) {
517
518
int retVal = 1;
if(execute_inproc) {
519
testItem->testCaseInit(xml_enabled);
520
521
testItem->testCase(0x0);
522
523
retVal = testItem->testCaseQuit();
524
525
526
} else {
int childpid = fork();
if(childpid == 0) {
527
testItem->testCaseInit(xml_enabled);
528
529
testItem->testCase(0x0);
530
531
exit(testItem->testCaseQuit());
532
533
534
535
} else {
int stat_lock = -1;
int child = wait(&stat_lock);
536
retVal = HandleChildProcessReturnValue(stat_lock);
537
538
539
540
541
542
543
}
}
return retVal;
}
544
545
546
/*!
* Prints usage information
*/
547
548
void
printUsage() {
549
printf("Usage: ./runner [--in-proc] [--suite SUITE] [--test TEST]\n");
550
551
printf(" [--name-contains SUBSTR] [--show-tests\n");
printf(" [--xml] [--xsl STYLESHEET] [--help]\n");
552
printf("Options:\n");
553
printf(" --in-proc Executes tests in-process\n");
554
printf(" --show-tests Prints out all the executable tests\n");
555
printf(" --xml Enables XML logger\n");
556
printf(" --xsl STYLESHEET Use the given file as XSL style sheet for XML\n");
557
558
559
560
printf(" -t --test TEST Executes only tests with given name\n");
printf(" -ts --name-contains SUBSTR Executes only tests that have given\n");
printf(" substring in test name\n");
printf(" -s --suite SUITE Executes only the given test suite\n");
561
562
printf(" -h --help Print this help\n");
563
564
}
565
566
567
/*!
* Parse command line arguments
568
569
570
*
* \param argc Count of command line arguments
* \param argv Array of commond lines arguments
571
572
573
574
575
576
577
578
*/
void
ParseOptions(int argc, char *argv[])
{
int i;
for (i = 1; i < argc; ++i) {
const char *arg = argv[i];
579
if(SDL_strcmp(arg, "--in-proc") == 0) {
580
581
execute_inproc = 1;
}
582
583
else if(SDL_strcmp(arg, "--show-tests") == 0) {
only_print_tests = 1;
584
}
585
586
587
else if(SDL_strcmp(arg, "--xml") == 0) {
xml_enabled = 1;
}
588
589
else if(SDL_strcmp(arg, "--test") == 0 || SDL_strcmp(arg, "-t") == 0) {
only_selected_test = 1;
590
591
592
593
594
595
596
597
598
char *testName = NULL;
if( (i + 1) < argc) {
testName = argv[++i];
} else {
printf("runner: test name is missing\n");
printUsage();
exit(1);
}
599
600
memset(selected_test_name, 0, NAME_BUFFER_SIZE);
601
602
strcpy(selected_test_name, testName);
}
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
else if(SDL_strcmp(arg, "--xsl") == 0) {
custom_xsl_enabled = 1;
char *stylesheet = NULL;
if( (i + 1) < argc) {
stylesheet = argv[++i];
} else {
printf("runner: filename of XSL stylesheet is missing\n");
printUsage();
exit(1);
}
memset(xsl_stylesheet_name, 0, NAME_BUFFER_SIZE);
strncpy(xsl_stylesheet_name, stylesheet, NAME_BUFFER_SIZE);
}
618
else if(SDL_strcmp(arg, "--name-contains") == 0 || SDL_strcmp(arg, "-ts") == 0) {
619
620
621
622
623
624
625
626
627
628
629
630
631
632
only_tests_with_string = 1;
char *substring = NULL;
if( (i + 1) < argc) {
substring = argv[++i];
} else {
printf("runner: substring of test name is missing\n");
printUsage();
exit(1);
}
memset(testcase_name_substring, 0, NAME_BUFFER_SIZE);
strcpy(testcase_name_substring, substring);
}
633
634
635
else if(SDL_strcmp(arg, "--suite") == 0 || SDL_strcmp(arg, "-s") == 0) {
only_selected_suite = 1;
636
637
638
639
640
641
642
643
644
645
char *suiteName = NULL;
if( (i + 1) < argc) {
suiteName = argv[++i];
} else {
printf("runner: suite name is missing\n");
printUsage();
exit(1);
}
memset(selected_suite_name, 0, NAME_BUFFER_SIZE);
646
647
strcpy(selected_suite_name, suiteName);
}
648
649
650
651
else if(SDL_strcmp(arg, "--help") == 0 || SDL_strcmp(arg, "-h") == 0) {
printUsage();
exit(0);
}
652
else {
653
654
printf("runner: unknown command '%s'\n", arg);
printUsage();
655
656
exit(0);
}
657
658
}
}
659
660
661
662
663
664
665
666
/*!
* Entry point for test runner
*
* \param argc Count of command line arguments
* \param argv Array of commond lines arguments
*/
667
668
669
670
int
main(int argc, char *argv[])
{
ParseOptions(argc, argv);
671
672
// print: Testing against SDL version fuu (rev: bar) if verbose == true
673
674
675
int totalTestfailureCount = 0, totalTestPassCount = 0;
int testFailureCount = 0, testPassCount = 0, testSkipCount = 0;
676
677
char *testSuiteName = NULL;
int suiteCounter = 0;
678
679
680
681
682
683
#if defined(linux) || defined( __linux)
char *extension = "so";
#else
char *extension = "dylib";
#endif
684
685
686
if(xml_enabled) {
SetupXMLLogger();
687
688
RunStarted(argc, argv, time(0), xsl_stylesheet_name);
689
690
} else {
SetupPlainLogger();
691
692
RunStarted(argc, argv, time(0), NULL);
693
}
694
695
const Uint32 startTicks = SDL_GetTicks();
696
697
TestSuiteReference *suites = ScanForTestSuites(DEFAULT_TEST_DIRECTORY, extension);
698
suites = LoadTestSuites(suites);
699
700
TestCase *testCases = LoadTestCases(suites);
701
702
703
704
705
706
707
708
709
710
711
// if --show-tests option is given, only print tests and exit
if(only_print_tests) {
TestCase *testItem = NULL;
for(testItem = testCases; testItem; testItem = testItem->next) {
printf("%s (in %s)\n", testItem->testName, testItem->suiteName);
}
return 0;
}
712
713
char *currentSuiteName = NULL;
714
715
int suiteStartTime = SDL_GetTicks();
716
TestCase *testItem = NULL;
717
for(testItem = testCases; testItem; testItem = testItem->next) {
718
719
if(currentSuiteName == NULL) {
currentSuiteName = testItem->suiteName;
720
SuiteStarted(currentSuiteName, time(0));
721
722
723
724
725
726
testFailureCount = testPassCount = 0;
suiteCounter++;
}
else if(strncmp(currentSuiteName, testItem->suiteName, NAME_BUFFER_SIZE) != 0) {
727
728
729
730
const double suiteRuntime = (SDL_GetTicks() - suiteStartTime) / 1000.0f;
SuiteEnded(testPassCount, testFailureCount, testSkipCount, time(0),
suiteRuntime);
731
732
733
suiteStartTime = SDL_GetTicks();
734
currentSuiteName = testItem->suiteName;
735
SuiteStarted(currentSuiteName, time(0));
736
737
738
739
testFailureCount = testPassCount = 0;
suiteCounter++;
740
741
742
}
TestStarted(testItem->testName, testItem->suiteName,
743
744
745
testItem->description, time(0));
const Uint32 testTimeStart = SDL_GetTicks();
746
747
748
int retVal = ExecuteTest(testItem);
if(retVal) {
749
750
totalTestfailureCount++;
testFailureCount++;
751
} else {
752
753
totalTestPassCount++;
testPassCount++;
754
}
755
756
757
758
const double testTotalRuntime = (SDL_GetTicks() - testTimeStart) / 1000.0f;
TestEnded(testItem->testName, testItem->suiteName, retVal, time(0), testTotalRuntime);
759
}
760
761
if(currentSuiteName) {
762
763
SuiteEnded(testPassCount, testFailureCount, testSkipCount, time(0),
(SDL_GetTicks() - suiteStartTime) / 1000.0f);
764
}
765
766
767
768
UnloadTestCases(testCases);
UnloadTestSuites(suites);
769
const Uint32 endTicks = SDL_GetTicks();
770
const double totalRunTime = (endTicks - startTicks) / 1000.0f;
771
772
RunEnded(totalTestPassCount + totalTestfailureCount, suiteCounter,
773
totalTestPassCount, totalTestfailureCount, time(0), totalRunTime);
774
775
return (totalTestfailureCount ? 1 : 0);
776
}