Remove NNG libraries from packages
[ric-plt/lib/rmr.git] / test / unit_test.ksh
1 #!/usr/bin/env ksh
2 # this has been hacked to work with bash; ksh is preferred
3
4 #==================================================================================
5 #        Copyright (c) 2019 Nokia
6 #        Copyright (c) 2018-2019 AT&T Intellectual Property.
7 #
8 #   Licensed under the Apache License, Version 2.0 (the "License");
9 #   you may not use this file except in compliance with the License.
10 #   You may obtain a copy of the License at
11 #
12 #       http://www.apache.org/licenses/LICENSE-2.0
13 #
14 #   Unless required by applicable law or agreed to in writing, software
15 #   distributed under the License is distributed on an "AS IS" BASIS,
16 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 #   See the License for the specific language governing permissions and
18 #   limitations under the License.
19 #==================================================================================
20
21
22 #
23 #       Mnemonic:       unit_test.ksh
24 #       Abstract:       Execute unit test(s) in the directory and produce a more
25 #                               meaningful summary than gcov gives by default (exclude
26 #                               coverage  on the unit test functions).
27 #
28 #                               Test files must be named *_test.c, or must explicitly be
29 #                               supplied on the command line. Functions in the test
30 #                               files will not be reported on provided that they have
31 #                               their prototype (all on the SAME line) as:
32 #                                       static type name() {
33 #
34 #                               Functions with coverage less than 80% will be reported as
35 #                               [LOW] in the output.  A file is considered to pass if the
36 #                               overall execution percentage for the file is >= 80% regardless
37 #                               of the number of functions that reported low.
38 #
39 #                               Test programmes are built prior to execution. Plan-9 mk is
40 #                               the preferred builder, but as it's not widly adopted (sigh)
41 #                               make is assumed and -M will shift to Plan-9. Use -C xxx to
42 #                               invoke a customised builder.
43 #
44 #                               For a module which does not pass, we will attempt to boost
45 #                               the coverage by discounting the unexecuted lines which are
46 #                               inside of if() statements that are checking return from
47 #                               (m)alloc() calls or are checking for nil pointers as these
48 #                               cases are likely impossible to drive. When discount testing
49 #                               is done both the failure message from the original analysis
50 #                               and a pass/fail message from the discount test are listed,
51 #                               but only the result of the discount test is taken into
52 #                               consideration with regard to overall success.
53 #
54 #                               Overall Pass/Fail
55 #                               By default the overall state is based only on the success
56 #                               or failure of the unit tests and NOT on the perceived
57 #                               state of coverage.  If the -s (strict) option is given, then
58 #                               overall state will be failure if code coverage expectations
59 #                               are not met.
60 #
61 #       Date:           16 January 2018
62 #       Author:         E. Scott Daniels
63 # -------------------------------------------------------------------------
64
65 function usage {
66         echo "usage: $0 [-G|-M|-C custom-command-string] [-a] [-c cov-target]  [-f] [-F] [-v] [-x]  [files]"
67         echo "  if -C is used to provide a custom build command then it must "
68         echo "  contain a %s which will be replaced with the unit test file name."
69         echo '  e.g.:  -C "mk -a %s"'
70         echo "  -a always run coverage (even on failed modules)"
71         echo "  -c allows user to set the target coverage for a module to pass; default is 80"
72         echo "  -f forces a discount check (normally done only if coverage < target)"
73         echo "  -F show only failures at the function level"
74         echo "  -s strict mode; code coverage must also pass to result in a good exit code"
75         echo "  -v will write additional information to the tty and save the disccounted file if discount run or -f given"
76         echo "  -x generates the coverage XML files for Sonar (implies -f)"
77 }
78
79 # read through the given file and add any functions that are static to the
80 # ignored list.  Only test and test tools files should be parsed.
81 #
82 function add_ignored_func {
83         if [[ ! -r $1 ]]
84         then
85                 echo ">>>> can't find file to ignore: $1"
86                 return
87         fi
88
89         typeset f=""
90         goop=$(
91                 grep "^static.*(.*).*{" $1 | awk '              # get list of test functions to ignore
92                         {
93                                 gsub( "[(].*", "" )
94                                 gsub( "[*]", "" )
95                                 if( $2 == "struct" ) {                  # static struct goober function
96                                         printf( "%s ", $4 )
97                                 } else {
98                                         printf( "%s ", $3 )                     # static goober-type funct
99                                 }
100                         }
101                 ' )
102
103         iflist="$iflist $goop"                  # this goop hack because bash can't read from a loop
104 }
105
106
107 # Merge two coverage files to preserve the total lines covered by different
108 # test programmes.
109 #
110 function merge_cov {
111         if [[ -z $1 || -z $2 ]]
112         then
113                 return
114         fi
115
116         if [[ ! -e $1 || ! -e $2 ]]
117         then
118                 return
119         fi
120
121         (
122                 cat $1
123                 echo "==merge=="
124                 cat $2
125         ) | awk '
126                 /^==merge==/ {
127                         merge = 1
128                         next
129                 }
130
131                 merge && /#####:/ {
132                         line = $2+0
133                         if( executed[line] ) {
134                                 $1 = sprintf( "%9d:", executed[line] )
135                         }
136                 }
137
138                 merge {
139                         print
140                         next
141                 }
142
143                 {
144                         line = $2+0
145                         if( $1+0 > 0 ) {
146                                 executed[line] = $1+0
147                         }
148                 }
149         '
150 }
151
152 #
153 #       Parse the .gcov file and discount any unexecuted lines which are in if()
154 #       blocks that are testing the result of alloc/malloc calls, or testing for
155 #       nil pointers.  The feeling is that these might not be possible to drive
156 #       and shoudn't contribute to coverage deficiencies.
157 #
158 #       In verbose mode, the .gcov file is written to stdout and any unexecuted
159 #       line which is discounted is marked with ===== replacing the ##### marking
160 #       that gcov wrote.
161 #
162 #       The return value is 0 for pass; non-zero for fail.
163 function discount_an_checks {
164         typeset f="$1"
165
166         mct=$( get_mct ${1%.gcov} )                     # see if a special coverage target is defined for this
167
168         if [[ ! -f $1 ]]
169         then
170                 if [[ -f ${1##*/} ]]
171                 then
172                         f=${1##*/}
173                 else
174                         echo "cant find: $f"
175                         return
176                 fi
177         fi
178
179         awk -v module_cov_target=$mct \
180                 -v cfail=${cfail:-WARN} \
181                 -v show_all=$show_all \
182                 -v full_name="${1}"  \
183                 -v module="${f%.*}"  \
184                 -v chatty=$chatty \
185                 -v replace_flags=$replace_flags \
186         '
187         function spit_line( ) {
188                 if( chatty ) {
189                         printf( "%s\n", $0 )
190                 }
191         }
192
193         /-:/ {                          # skip unexecutable lines
194                 spit_line()
195                 seq++                                   # allow blank lines in a sequence group
196                 next
197         }
198
199         {
200                 nexec++                 # number of executable lines
201         }
202
203         /#####:/ {
204                 unexec++;
205                 if( $2+0 != seq+1 ) {
206                         prev_malloc = 0
207                         prev_if = 0
208                         seq = 0
209                         spit_line()
210                         next
211                 }
212
213                 if( prev_if && prev_malloc ) {
214                         if( prev_malloc ) {
215                                 #printf( "allow discount: %s\n", $0 )
216                                 if( replace_flags ) {
217                                         gsub( "#####", "    1", $0 )
218                                 }
219                                 discount++;
220                         }
221                 }
222
223                 seq++;;
224                 spit_line()
225                 next;
226         }
227
228         /if[(].*alloc.*{/ {                     # if( (x = malloc( ... )) != NULL ) or if( (p = sym_alloc(...)) != NULL )
229                 seq = $2+0
230                 prev_malloc = 1
231                 prev_if = 1
232                 spit_line()
233                 next
234         }
235
236         /if[(].* == NULL/ {                             # a nil check likely not easily forced if it wasnt driven
237                 prev_malloc = 1
238                 prev_if = 1
239                 spit_line()
240                 seq = $2+0
241                 next
242         }
243
244         /if[(]/ {
245                 if( seq+1 == $2+0 && prev_malloc ) {            // malloc on previous line
246                         prev_if = 1
247                 } else {
248                         prev_malloc = 0
249                         prev_if = 0
250                 }
251                 spit_line()
252                 next
253         }
254
255         /alloc[(]/ {
256                 seq = $2+0
257                 prev_malloc = 1
258                 spit_line()
259                 next
260         }
261
262         {
263                 spit_line()
264         }
265
266         END {
267                 net = unexec - discount
268                 orig_cov = ((nexec-unexec)/nexec)*100           # original coverage
269                 adj_cov = ((nexec-net)/nexec)*100                       # coverage after discount
270                 pass_fail = adj_cov < module_cov_target ? cfail : "PASS"
271                 rc = adj_cov < module_cov_target ? 1 : 0
272                 if( pass_fail == cfail || show_all ) {
273                         if( chatty ) {
274                                 printf( "[%s] %s executable=%d unexecuted=%d discounted=%d net_unex=%d  cov=%d%% ==> %d%%  target=%d%%\n",
275                                         pass_fail, full_name ? full_name : module, nexec, unexec, discount, net, orig_cov, adj_cov, module_cov_target )
276                         } else {
277                                 printf( "[%s] %d%% (%d%%) %s\n", pass_fail, adj_cov, orig_cov, full_name ? full_name : module )
278                         }
279                 }
280
281                 exit( rc )
282         }
283         ' $f
284 }
285
286 # Given a file name ($1) see if it is in the ./.targets file. If it is
287 # return the coverage listed, else return (echo)  the default $module_cov_target
288 #
289 function get_mct {
290         typeset v=$module_cov_target
291
292         if [[ -f ./.targets ]]
293         then
294                 grep "^$1 " ./.targets | head -1 | read stuff
295                 tv="${stuff##* }"                                       # assume junk tv; ditch junk
296         fi
297
298         echo ${tv:-$v}
299 }
300
301 # Remove unneeded coverage files, then generate the xml files that can be given
302 # to sonar.  gcov.xml is based on the "raw" coverage and dcov.xml is based on
303 # the discounted coverage.
304 #
305 function mk_xml {
306         rm -fr *_test.c.gcov test_*.c.gcov *_test.c.dcov test_*.c.dcov          # we don't report on the unit test code, so ditch
307         cat *.gcov | cov2xml.ksh >gcov.xml
308         cat *.dcov | cov2xml.ksh >dcov.xml
309 }
310
311
312 # -----------------------------------------------------------------------------------------------------------------
313
314 if [[ -z $BUILD_PATH ]]
315 then
316
317         # we assume that the project has been built in the ../[.]build directory
318         if [[ -d ../build ]]
319         then
320                 export BUILD_PATH=../build
321         else
322                 if [[ -d ../.build ]]
323                 then
324                         export BUILD_PATH=../.build
325                 else
326                         echo "[WARN] cannot find build directory (tried ../build and ../.build); things might not work"
327                         echo ""
328                 fi
329         fi
330 fi
331
332 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$BUILD_PATH/lib:$BUILD_PATH/lib64
333 export C_INCLUDE_PATH=$C_INCLUDE_PATH:$BUILD_PATH/include
334 export LIBRARY_PATH=$LD_LIBRARY_PATH
335
336 # The Makefile sets specific includes for things
337 #export C_INCLUDE_PATH="../src/rmr/common/include:../src/rmr/si/include:$C_INCLUDE_PATH"
338
339 module_cov_target=80
340 builder="make -B %s"                                                    # default to plain ole make
341 verbose=0
342 show_all=1                                                                              # show all things -F sets to show failures only
343 strict=0                                                                                # -s (strict) will set; when off, coverage state ignored in final pass/fail
344 show_output=0                                                                   # show output from each test execution (-S)
345 quiet=0
346 gen_xml=0
347 replace_flags=1                                                                 # replace ##### in gcov for discounted lines
348 run_nano_tests=0                                                                # can nolonger be turned on
349 run_nng_tests=0                                                                 # -N will enable
350 always_gcov=0                                                                   # -a sets to always run gcov even if failure
351 save_gcov=1                                                                             # -o turns this off
352 out_dir=${UT_COVERAGE_DIR:-/tmp/rmr_gcov}               # -O changes output directory
353
354 export RMR_WARNING=1                                                    # turn on warnings
355
356 ulimit -c unlimited
357
358 while [[ $1 == "-"* ]]
359 do
360         case $1 in
361                 -C)     builder="$2"; shift;;           # custom build command
362                 -G)     builder="gmake %s";;
363                 -M)     builder="mk -a %s";;            # use plan-9 mk (better, but sadly not widly used)
364                 -N)     run_nng_tests=1;;
365                 -O)     out_dir=$2; shift;;
366
367                 -a)     always_gcov=1;;
368                 -c)     module_cov_target=$2; shift;;
369                 -e)     capture_file=$2; >$capture_file; shift;;                # capture errors from failed tests rather than spewing on tty
370                 -f)     force_discounting=1;
371                         trigger_discount_str="WARN|FAIL|PASS"           # check all outcomes for each module
372                         ;;
373
374                 -F)     show_all=0;;
375
376                 -n)     noexec=1;;
377                 -o)     save_gcov=0;;
378                 -s)     strict=1;;                                      # coverage counts toward pass/fail state
379                 -S)     show_output=1;;                         # test output shown even on success
380                 -v)     (( verbose++ ));;
381                 -q)     quiet=1;;                                       # less chatty when spilling error log files
382                 -x)     gen_xml=1
383                         force_discounting=1
384                         trigger_discount_str="WARN|FAIL|PASS"           # check all outcomes for each module
385                         rm -fr *cov.xml
386                         ;;
387
388
389                 -h)             usage; exit 0;;
390                 --help) usage; exit 0;;
391                 -\?)    usage; exit 0;;
392
393                 *)      echo "unrecognised option: $1" >&2
394                         usage >&2
395                         exit 1
396                         ;;
397         esac
398
399         shift
400 done
401
402
403 if (( strict ))                 # if in strict mode, coverage shortcomings are failures
404 then
405         cfail="FAIL"
406 else
407         cfail="WARN"
408 fi
409 if [[ -z $trigger_discount_str ]]
410 then
411         trigger_discount_str="$cfail"
412 fi
413
414
415 if [[ -z $1 ]]
416 then
417         flist=""
418         for tfile in *_test.c
419         do
420                 if [[ $tfile != *"static_test.c" ]]
421                 then
422                         if (( ! run_nng_tests )) && [[ $tfile == *"nng"* ]]             # drop any nng file unless -N given
423                         then
424                                 continue
425                         fi
426                         if [[ $tfile == *"nano"* ]]                     # no longer support nano tests; drop regardless
427                         then
428                                 continue
429                         fi
430
431                         echo "<INFO> add test: $tfile" >&2
432                         flist="${flist}$tfile "
433                 fi
434         done
435 else
436         flist="$@"
437 fi
438
439
440 if (( noexec ))
441 then
442         echo "no exec mode; would test these:"
443         for tf in $flist
444         do
445                 echo "  $tf"
446         done
447         exit 0
448 fi
449
450 rm -fr *.gcov                   # ditch the previous coverage files
451 ut_errors=0                     # unit test errors (not coverage errors)
452 errors=0
453
454 if ! touch /tmp/PPID$$.noise
455 then
456         echo "<ERR> unable to write to /tmp???"
457 fi
458
459 for tfile in $flist
460 do
461         for x in *.gcov
462         do
463                 if [[ -e $x ]]
464                 then
465                         cp $x $x-
466                 fi
467         done
468
469         echo "$tfile --------------------------------------"
470         (       # all noise is now captured into a tmp file to support quiet mode
471                 bcmd=$( printf "$builder" "${tfile%.c}" )
472                 if ! $bcmd >/tmp/PID$$.log 2>&1
473                 then
474                         echo "[FAIL] cannot build $tfile"
475                         cat /tmp/PID$$.log
476                         # do NOT remove tmp files; bash seens to not gen a new PID for subshells
477                         exit 1
478                 fi
479
480                 iflist="main sig_clean_exit "           # ignore external functions from our tools
481                 add_ignored_func $tfile                         # ignore all static functions in our test driver
482                 add_ignored_func test_support.c         # ignore all static functions in our test tools
483                 add_ignored_func test_nng_em.c          # the nng/nano emulated things
484                 add_ignored_func test_si95_em.c         # the si emulated things
485                 add_ignored_func test_common_em.c       # the common emulation functions
486                 for f in *_static_test.c                        # all static modules here
487                 do
488                         if(( ! run_nano_tests )) && [[ $f == *"nano"* ]]
489                         then
490                                 continue
491                         fi
492
493                         add_ignored_func $f
494                 done
495
496                 if ! ./${tfile%.c} >/tmp/PID$$.log 2>&1
497                 then
498                         echo "[FAIL] unit test failed for: $tfile"
499                         if [[ -n $capture_file ]]
500                         then
501                                 echo "all errors captured in $capture_file, listing only fail message on tty"
502                                 echo "$tfile --------------------------------------" >>$capture_file
503                                 cat /tmp/PID$$.log >>$capture_file
504                                 grep "^<FAIL>" /tmp/PID$$.log
505                                 echo ""
506                         else
507                                 if (( quiet ))
508                                 then
509                                         grep "^<" /tmp/PID$$.log|egrep -v "^<SIEM>|^<EM>"       # in quiet mode just dump <...> messages which are assumed from the test programme not appl
510                                 else
511                                         cat /tmp/PID$$.log
512                                 fi
513                         fi
514                         (( ut_errors++ ))                               # cause failure even if not in strict mode
515                         if (( ! always_gcov ))
516                         then
517                                 exit 1                                          # we are in a subshell, must exit bad
518                         fi
519                 else
520                         if (( show_output ))
521                         then
522                                 printf "\n============= test programme output =======================\n"
523                                 cat /tmp/PID$$.log
524                                 printf "===========================================================\n"
525                         fi
526                 fi
527
528                 (
529                         touch ./.targets
530                         sed '/^#/ d; /^$/ d; s/^/TARGET: /' ./.targets
531                         gcov -f ${tfile%.c} | sed "s/'//g"
532                 ) | awk \
533                         -v cfail=$cfail \
534                         -v show_all=$show_all \
535                         -v ignore_list="$iflist" \
536                         -v module_cov_target=$module_cov_target \
537                         -v chatty=$verbose \
538                         '
539                         BEGIN {
540                                 announce_target = 1;
541                                 nignore = split( ignore_list, ignore, " " )
542                                 for( i = 1; i <= nignore; i++ ) {
543                                         imap[ignore[i]] = 1
544                                 }
545
546                                 exit_code = 0           # assume good
547                         }
548
549                         /^TARGET:/ {
550                                 if( NF > 1 ) {
551                                         target[$2] = $NF
552                                 }
553                                 next;
554                         }
555
556                         /File.*_test/ || /File.*test_/ {                # dont report on test files
557                                 skip = 1
558                                 file = 1
559                                 fname = $2
560                                 next
561                         }
562
563                         /File/ {
564                                 skip = 0
565                                 file = 1
566                                 fname = $2
567                                 next
568                         }
569
570                         /Function/ {
571                                 fname = $2
572                                 file = 0
573                                 if( imap[fname] ) {
574                                         fname = "skipped: " fname               # should never see and make it smell if we do
575                                         skip = 1
576                                 } else {
577                                         skip = 0
578                                 }
579                                 next
580                         }
581
582                         skip { next }
583
584                         /Lines executed/ {
585                                 split( $0, a, ":" )
586                                 pct = a[2]+0
587
588                                 if( file ) {
589                                         if( announce_target ) {                         # announce default once at start
590                                                 announce_target = 0;
591                                                 printf( "\n[INFO] default target coverage for modules is %d%%\n", module_cov_target )
592                                         }
593
594                                         if( target[fname] ) {
595                                                 mct = target[fname]
596                                                 announce_target = 1;
597                                         } else {
598                                                 mct = module_cov_target
599                                         }
600
601                                         if( announce_target ) {                                 # annoucne for module if different from default
602                                                 printf( "[INFO] target coverage for %s is %d%%\n", fname, mct )
603                                         }
604
605                                         if( pct < mct ) {
606                                                 printf( "[%s] %3d%% %s\n", cfail, pct, fname )  # CAUTION: write only 3 things  here
607                                                 exit_code = 1
608                                         } else {
609                                                 printf( "[PASS] %3d%% %s\n", pct, fname )
610                                         }
611
612                                         announce_target = 0;
613                                 } else {
614                                         if( pct < 70 ) {
615                                                 printf( "[LOW]  %3d%% %s\n", pct, fname )
616                                         } else {
617                                                 if( pct < 80 ) {
618                                                         printf( "[MARG] %3d%% %s\n", pct, fname )
619                                                 } else {
620                                                         if( show_all ) {
621                                                                 printf( "[OK]   %3d%% %s\n", pct, fname )
622                                                         }
623                                                 }
624                                         }
625                                 }
626                         }
627
628                         END {
629                                 printf( "\n" );
630                                 exit( exit_code )
631                         }
632                 ' >/tmp/PID$$.log                                       # capture output to run discount on failures
633                 rc=$?
634                 cat /tmp/PID$$.log
635
636                 if (( rc  || force_discounting ))       # didn't pass, or forcing, see if discounting helps
637                 then
638                         if (( ! verbose ))
639                         then
640                                 echo "[INFO] checking to see if discounting improves coverage for failures listed above"
641                         fi
642
643                         # preferred, but breaks under bash
644                         #egrep "$trigger_discount_str"  /tmp/PID$$.log | while read state junk  name
645                         egrep "$trigger_discount_str"  /tmp/PID$$.log | while read stuff
646                         do
647                                 set stuff                       # this hack required because bash cant read into mult vars
648                                 state="$1"
649                                 name="$3"
650
651                                 if ! discount_an_checks $name.gcov >/tmp/PID$$.disc
652                                 then
653                                         (( errors++ ))
654                                 fi
655
656                                 tail -1 /tmp/PID$$.disc | grep '\['
657
658                                 if (( verbose > 1 ))                    # updated file was generated, keep here
659                                 then
660                                         echo "[INFO] discounted coverage info in: ${tfile##*/}.dcov"
661                                 fi
662
663                                 mv /tmp/PID$$.disc ${name##*/}.dcov
664                         done
665                 fi
666         )>/tmp/PID$$.noise 2>&1
667         if (( $? != 0 ))
668         then
669                 (( ut_errors++ ))
670                 cat /tmp/PID$$.noise
671                 continue
672         fi
673
674         for x in *.gcov                                                 # merge any previous coverage file with this one
675         do
676                 if [[ -e $x && -e $x- ]]
677                 then
678                         merge_cov $x $x- >/tmp/PID$$.mc
679                         cp /tmp/PID$$.mc $x
680                         rm $x-
681                 fi
682         done
683
684         if (( ! quiet ))
685         then
686                 cat /tmp/PID$$.noise
687         fi
688 done
689
690 echo ""
691 echo "[INFO] final discount checks on merged gcov files"
692 show_all=1
693 for xx in *.gcov
694 do
695         if [[ $xx != *"test"* ]]
696         then
697                 of=${xx%.gcov}.dcov
698                 discount_an_checks $xx  >$of
699                 if [[ -n $of ]]
700                 then
701                         tail -1 $of |  grep '\['
702                 fi
703         fi
704 done
705
706 if (( save_gcov ))
707 then
708         echo ""
709         ok=1
710         if [[ ! -d $outdir ]]
711         then
712                 if ! mkdir -p $out_dir
713                 then
714                         echo "[WARN] unable to save .gcov files in $out_dir"
715                         ok=0
716                 fi
717         fi
718
719         if (( ok ))
720         then
721                 rm -fr $out_dir/*
722                 echo "[INFO] gcov files saved in $out_dir for push to remote system(s)"
723                 cp *.gcov $out_dir/
724                 rm -f $out_dir/*_test.c.gcov $out_dir/test_*.c.gcov
725                 rm -f ./*_test.c.gcov ./test_*.c.gcov
726         fi
727 else
728         echo "[INFO] .gcov files were not saved for remote system"
729 fi
730
731 state=0                                         # final state
732 rm -f /tmp/PID$$.*
733 if (( strict ))                         # fail if some coverage failed too
734 then
735         if (( errors + ut_errors ))
736         then
737                 state=1
738         fi
739 else                                            # not strict; fail only if unit tests themselves failed
740         if (( ut_errors ))
741         then
742                 state=1
743         fi
744 fi
745
746 echo""
747 if (( state ))
748 then
749         echo "[FAIL] overall unit testing fails: coverage errors=$errors   unit test errors=$ut_errors"
750 else
751         echo "[PASS] overall unit testing passes"
752         if (( gen_xml ))
753         then
754                 mk_xml
755         fi
756 fi
757
758 exit $state
759