xref: /aosp_15_r20/external/rappor/regtest.sh (revision 2abb31345f6c95944768b5222a9a5ed3fc68cc00)
1*2abb3134SXin Li#!/bin/bash
2*2abb3134SXin Liusage() {
3*2abb3134SXin Liecho "
4*2abb3134SXin Li Run end-to-end tests in parallel.
5*2abb3134SXin Li
6*2abb3134SXin Li Usage:
7*2abb3134SXin Li   ./regtest.sh <function name>
8*2abb3134SXin Li At the end, it will print an HTML summary.
9*2abb3134SXin Li
10*2abb3134SXin Li Three main functions are
11*2abb3134SXin Li    run [<pattern> [<lang>]] - run tests matching <pattern> in
12*2abb3134SXin Li                                       parallel. The language
13*2abb3134SXin Li                                       of the client to use.
14*2abb3134SXin Li    run-seq [<pattern> [<lang>]] - ditto, except that tests are run
15*2abb3134SXin Li                                       sequentially
16*2abb3134SXin Li    run-all                      - run all tests, in parallel
17*2abb3134SXin Li
18*2abb3134SXin Li Examples:
19*2abb3134SXin Li $ ./regtest.sh run-seq unif-small-typical  # Run, the unif-small-typical test
20*2abb3134SXin Li $ ./regtest.sh run-seq unif-small-         # Sequential, the tests containing:
21*2abb3134SXin Li                                            # 'unif-small-'
22*2abb3134SXin Li $ ./regtest.sh run unif-  # Parallel run, matches multiple cases
23*2abb3134SXin Li $ ./regtest.sh run-all    # Run all tests
24*2abb3134SXin Li
25*2abb3134SXin Li The <pattern> argument is a regex in 'grep -E' format. (Detail: Don't
26*2abb3134SXin Li use $ in the pattern, since it matches the whole spec line and not just the
27*2abb3134SXin Li test case name.) The number of processors used in a parallel run is one less
28*2abb3134SXin Li than the number of CPUs on the machine.
29*2abb3134SXin Li"
30*2abb3134SXin Li}
31*2abb3134SXin Li# Future speedups:
32*2abb3134SXin Li# - Reuse the same input -- come up with naming scheme based on params
33*2abb3134SXin Li# - Reuse the same maps -- ditto, rappor library can cache it
34*2abb3134SXin Li#
35*2abb3134SXin Li
36*2abb3134SXin Liset -o nounset
37*2abb3134SXin Liset -o pipefail
38*2abb3134SXin Liset -o errexit
39*2abb3134SXin Li
40*2abb3134SXin Li. util.sh
41*2abb3134SXin Li
42*2abb3134SXin Lireadonly THIS_DIR=$(dirname $0)
43*2abb3134SXin Lireadonly REPO_ROOT=$THIS_DIR
44*2abb3134SXin Lireadonly CLIENT_DIR=$REPO_ROOT/client/python
45*2abb3134SXin Li# subdirs are in _tmp/$impl, which shouldn't overlap with anything else in _tmp
46*2abb3134SXin Lireadonly REGTEST_BASE_DIR=_tmp
47*2abb3134SXin Li
48*2abb3134SXin Li# All the Python tools need this
49*2abb3134SXin Liexport PYTHONPATH=$CLIENT_DIR
50*2abb3134SXin Li
51*2abb3134SXin Liprint-unique-values() {
52*2abb3134SXin Li  local num_unique_values=$1
53*2abb3134SXin Li  seq 1 $num_unique_values | awk '{print "v" $1}'
54*2abb3134SXin Li}
55*2abb3134SXin Li
56*2abb3134SXin Li# Add some more candidates here.  We hope these are estimated at 0.
57*2abb3134SXin Li# e.g. if add_start=51, and num_additional is 20, show v51-v70
58*2abb3134SXin Limore-candidates() {
59*2abb3134SXin Li  local last_true=$1
60*2abb3134SXin Li  local num_additional=$2
61*2abb3134SXin Li
62*2abb3134SXin Li  local begin
63*2abb3134SXin Li  local end
64*2abb3134SXin Li  begin=$(expr $last_true + 1)
65*2abb3134SXin Li  end=$(expr $last_true + $num_additional)
66*2abb3134SXin Li
67*2abb3134SXin Li  seq $begin $end | awk '{print "v" $1}'
68*2abb3134SXin Li}
69*2abb3134SXin Li
70*2abb3134SXin Li# Args:
71*2abb3134SXin Li#   unique_values: File of unique true values
72*2abb3134SXin Li#   last_true: last true input, e.g. 50 if we generated "v1" .. "v50".
73*2abb3134SXin Li#   num_additional: additional candidates to generate (starting at 'last_true')
74*2abb3134SXin Li#   to_remove: Regex of true values to omit from the candidates list, or the
75*2abb3134SXin Li#     string 'NONE' if none should be.  (Our values look like 'v1', 'v2', etc. so
76*2abb3134SXin Li#     there isn't any ambiguity.)
77*2abb3134SXin Liprint-candidates() {
78*2abb3134SXin Li  local unique_values=$1
79*2abb3134SXin Li  local last_true=$2
80*2abb3134SXin Li  local num_additional=$3
81*2abb3134SXin Li  local to_remove=$4
82*2abb3134SXin Li
83*2abb3134SXin Li  if test $to_remove = NONE; then
84*2abb3134SXin Li    cat $unique_values  # include all true inputs
85*2abb3134SXin Li  else
86*2abb3134SXin Li    egrep -v $to_remove $unique_values  # remove some true inputs
87*2abb3134SXin Li  fi
88*2abb3134SXin Li  more-candidates $last_true $num_additional
89*2abb3134SXin Li}
90*2abb3134SXin Li
91*2abb3134SXin Li# Generate a single test case, specified by a line of the test spec.
92*2abb3134SXin Li# This is a helper function for _run_tests().
93*2abb3134SXin Li_setup-one-case() {
94*2abb3134SXin Li  local impl=$1
95*2abb3134SXin Li  shift  # impl is not part of the spec; the next 13 params are
96*2abb3134SXin Li
97*2abb3134SXin Li  local test_case=$1
98*2abb3134SXin Li
99*2abb3134SXin Li  # input params
100*2abb3134SXin Li  local dist=$2
101*2abb3134SXin Li  local num_unique_values=$3
102*2abb3134SXin Li  local num_clients=$4
103*2abb3134SXin Li  local values_per_client=$5
104*2abb3134SXin Li
105*2abb3134SXin Li  # RAPPOR params
106*2abb3134SXin Li  local num_bits=$6
107*2abb3134SXin Li  local num_hashes=$7
108*2abb3134SXin Li  local num_cohorts=$8
109*2abb3134SXin Li  local p=$9
110*2abb3134SXin Li  local q=${10}  # need curly braces to get the 10th arg
111*2abb3134SXin Li  local f=${11}
112*2abb3134SXin Li
113*2abb3134SXin Li  # map params
114*2abb3134SXin Li  local num_additional=${12}
115*2abb3134SXin Li  local to_remove=${13}
116*2abb3134SXin Li
117*2abb3134SXin Li  banner 'Setting up parameters and candidate files for '$test_case
118*2abb3134SXin Li
119*2abb3134SXin Li  local case_dir=$REGTEST_BASE_DIR/$impl/$test_case
120*2abb3134SXin Li  mkdir --verbose -p $case_dir
121*2abb3134SXin Li
122*2abb3134SXin Li  # Save the "spec"
123*2abb3134SXin Li  echo "$@" > $case_dir/spec.txt
124*2abb3134SXin Li
125*2abb3134SXin Li  local params_path=$case_dir/case_params.csv
126*2abb3134SXin Li
127*2abb3134SXin Li  echo 'k,h,m,p,q,f' > $params_path
128*2abb3134SXin Li  echo "$num_bits,$num_hashes,$num_cohorts,$p,$q,$f" >> $params_path
129*2abb3134SXin Li
130*2abb3134SXin Li  print-unique-values $num_unique_values > $case_dir/case_unique_values.txt
131*2abb3134SXin Li
132*2abb3134SXin Li  local true_map_path=$case_dir/case_true_map.csv
133*2abb3134SXin Li
134*2abb3134SXin Li  bin/hash_candidates.py \
135*2abb3134SXin Li    $params_path \
136*2abb3134SXin Li    < $case_dir/case_unique_values.txt \
137*2abb3134SXin Li    > $true_map_path
138*2abb3134SXin Li
139*2abb3134SXin Li  # banner "Constructing candidates"
140*2abb3134SXin Li
141*2abb3134SXin Li  print-candidates \
142*2abb3134SXin Li    $case_dir/case_unique_values.txt $num_unique_values \
143*2abb3134SXin Li    $num_additional "$to_remove" \
144*2abb3134SXin Li    > $case_dir/case_candidates.txt
145*2abb3134SXin Li
146*2abb3134SXin Li  # banner "Hashing candidates to get 'map'"
147*2abb3134SXin Li
148*2abb3134SXin Li  bin/hash_candidates.py \
149*2abb3134SXin Li    $params_path \
150*2abb3134SXin Li    < $case_dir/case_candidates.txt \
151*2abb3134SXin Li    > $case_dir/case_map.csv
152*2abb3134SXin Li}
153*2abb3134SXin Li
154*2abb3134SXin Li# Run a single test instance, specified by <test_name, instance_num>.
155*2abb3134SXin Li# This is a helper function for _run_tests().
156*2abb3134SXin Li_run-one-instance() {
157*2abb3134SXin Li  local test_case=$1
158*2abb3134SXin Li  local test_instance=$2
159*2abb3134SXin Li  local impl=$3
160*2abb3134SXin Li
161*2abb3134SXin Li  local case_dir=$REGTEST_BASE_DIR/$impl/$test_case
162*2abb3134SXin Li
163*2abb3134SXin Li  read -r \
164*2abb3134SXin Li    case_name distr num_unique_values num_clients values_per_client \
165*2abb3134SXin Li    num_bits num_hashes num_cohorts p q f \
166*2abb3134SXin Li    num_additional to_remove \
167*2abb3134SXin Li    < $case_dir/spec.txt
168*2abb3134SXin Li
169*2abb3134SXin Li  local instance_dir=$case_dir/$test_instance
170*2abb3134SXin Li  mkdir --verbose -p $instance_dir
171*2abb3134SXin Li
172*2abb3134SXin Li  banner "Generating reports (gen_reports.R)"
173*2abb3134SXin Li
174*2abb3134SXin Li  # the TRUE_VALUES_PATH environment variable can be used to avoid
175*2abb3134SXin Li  # generating new values every time.  NOTE: You are responsible for making
176*2abb3134SXin Li  # sure the params match!
177*2abb3134SXin Li
178*2abb3134SXin Li  local true_values=${TRUE_VALUES_PATH:-}
179*2abb3134SXin Li  if test -z "$true_values"; then
180*2abb3134SXin Li    true_values=$instance_dir/case_true_values.csv
181*2abb3134SXin Li    tests/gen_true_values.R $distr $num_unique_values $num_clients \
182*2abb3134SXin Li                            $values_per_client $num_cohorts \
183*2abb3134SXin Li                            $true_values
184*2abb3134SXin Li  else
185*2abb3134SXin Li    # TEMP hack: Make it visible to plot.
186*2abb3134SXin Li    # TODO: Fix compare_dist.R
187*2abb3134SXin Li    ln -s -f --verbose \
188*2abb3134SXin Li      $PWD/$true_values \
189*2abb3134SXin Li      $instance_dir/case_true_values.csv
190*2abb3134SXin Li  fi
191*2abb3134SXin Li
192*2abb3134SXin Li  case $impl in
193*2abb3134SXin Li    python)
194*2abb3134SXin Li      banner "Running RAPPOR Python client"
195*2abb3134SXin Li
196*2abb3134SXin Li      # Writes encoded "out" file, true histogram, true inputs to
197*2abb3134SXin Li      # $instance_dir.
198*2abb3134SXin Li      time tests/rappor_sim.py \
199*2abb3134SXin Li        --num-bits $num_bits \
200*2abb3134SXin Li        --num-hashes $num_hashes \
201*2abb3134SXin Li        --num-cohorts $num_cohorts \
202*2abb3134SXin Li        -p $p \
203*2abb3134SXin Li        -q $q \
204*2abb3134SXin Li        -f $f \
205*2abb3134SXin Li        < $true_values \
206*2abb3134SXin Li        > "$instance_dir/case_reports.csv"
207*2abb3134SXin Li      ;;
208*2abb3134SXin Li
209*2abb3134SXin Li    cpp)
210*2abb3134SXin Li      banner "Running RAPPOR C++ client (see rappor_sim.log for errors)"
211*2abb3134SXin Li
212*2abb3134SXin Li      time client/cpp/_tmp/rappor_sim \
213*2abb3134SXin Li        $num_bits \
214*2abb3134SXin Li        $num_hashes \
215*2abb3134SXin Li        $num_cohorts \
216*2abb3134SXin Li        $p \
217*2abb3134SXin Li        $q \
218*2abb3134SXin Li        $f \
219*2abb3134SXin Li        < $true_values \
220*2abb3134SXin Li        > "$instance_dir/case_reports.csv" \
221*2abb3134SXin Li        2>"$instance_dir/rappor_sim.log"
222*2abb3134SXin Li      ;;
223*2abb3134SXin Li
224*2abb3134SXin Li    *)
225*2abb3134SXin Li      log "Invalid impl $impl (should be one of python|cpp)"
226*2abb3134SXin Li      exit 1
227*2abb3134SXin Li    ;;
228*2abb3134SXin Li
229*2abb3134SXin Li  esac
230*2abb3134SXin Li
231*2abb3134SXin Li  banner "Summing RAPPOR IRR bits to get 'counts'"
232*2abb3134SXin Li
233*2abb3134SXin Li  bin/sum_bits.py \
234*2abb3134SXin Li    $case_dir/case_params.csv \
235*2abb3134SXin Li    < $instance_dir/case_reports.csv \
236*2abb3134SXin Li    > $instance_dir/case_counts.csv
237*2abb3134SXin Li
238*2abb3134SXin Li  local out_dir=${instance_dir}_report
239*2abb3134SXin Li  mkdir --verbose -p $out_dir
240*2abb3134SXin Li
241*2abb3134SXin Li  # Currently, the summary file shows and aggregates timing of the inference
242*2abb3134SXin Li  # engine, which excludes R's loading time and reading of the (possibly
243*2abb3134SXin Li  # substantial) map file. Timing below is more inclusive.
244*2abb3134SXin Li  TIMEFORMAT='Running compare_dist.R took %R seconds'
245*2abb3134SXin Li  time {
246*2abb3134SXin Li    # Input prefix, output dir
247*2abb3134SXin Li    tests/compare_dist.R -t "Test case: $test_case (instance $test_instance)" \
248*2abb3134SXin Li                         "$case_dir/case" "$instance_dir/case" $out_dir
249*2abb3134SXin Li  }
250*2abb3134SXin Li}
251*2abb3134SXin Li
252*2abb3134SXin Li# Like _run-once-case, but log to a file.
253*2abb3134SXin Li_run-one-instance-logged() {
254*2abb3134SXin Li  local test_case=$1
255*2abb3134SXin Li  local test_instance=$2
256*2abb3134SXin Li  local impl=$3
257*2abb3134SXin Li
258*2abb3134SXin Li  local log_dir=$REGTEST_BASE_DIR/$impl/$test_case/${test_instance}_report
259*2abb3134SXin Li  mkdir --verbose -p $log_dir
260*2abb3134SXin Li
261*2abb3134SXin Li  log "Started '$test_case' (instance $test_instance) -- logging to $log_dir/log.txt"
262*2abb3134SXin Li  _run-one-instance "$@" >$log_dir/log.txt 2>&1 \
263*2abb3134SXin Li    && log "Test case $test_case (instance $test_instance) done" \
264*2abb3134SXin Li    || log "Test case $test_case (instance $test_instance) failed"
265*2abb3134SXin Li}
266*2abb3134SXin Li
267*2abb3134SXin Limake-summary() {
268*2abb3134SXin Li  local dir=$1
269*2abb3134SXin Li  local impl=$2
270*2abb3134SXin Li
271*2abb3134SXin Li  local filename=results.html
272*2abb3134SXin Li
273*2abb3134SXin Li  tests/make_summary.py $dir $dir/rows.html
274*2abb3134SXin Li
275*2abb3134SXin Li  pushd $dir >/dev/null
276*2abb3134SXin Li
277*2abb3134SXin Li  cat ../../tests/regtest.html \
278*2abb3134SXin Li    | sed -e '/__TABLE_ROWS__/ r rows.html' -e "s/_IMPL_/$impl/g" \
279*2abb3134SXin Li    > $filename
280*2abb3134SXin Li
281*2abb3134SXin Li  popd >/dev/null
282*2abb3134SXin Li
283*2abb3134SXin Li  log "Wrote $dir/$filename"
284*2abb3134SXin Li  log "URL: file://$PWD/$dir/$filename"
285*2abb3134SXin Li}
286*2abb3134SXin Li
287*2abb3134SXin Litest-error() {
288*2abb3134SXin Li  local spec_regex=${1:-}
289*2abb3134SXin Li  log "Some test cases failed"
290*2abb3134SXin Li  if test -n "$spec_regex"; then
291*2abb3134SXin Li    log "(Perhaps none matched pattern '$spec_regex')"
292*2abb3134SXin Li  fi
293*2abb3134SXin Li  # don't quit just yet
294*2abb3134SXin Li  # exit 1
295*2abb3134SXin Li}
296*2abb3134SXin Li
297*2abb3134SXin Li# Assuming the spec file, write a list of test case names (first column) with
298*2abb3134SXin Li# the instance ids (second column), where instance ids run from 1 to $1.
299*2abb3134SXin Li# Third column is impl.
300*2abb3134SXin Li_setup-test-instances() {
301*2abb3134SXin Li  local instances=$1
302*2abb3134SXin Li  local impl=$2
303*2abb3134SXin Li
304*2abb3134SXin Li  while read line; do
305*2abb3134SXin Li    for i in $(seq 1 $instances); do
306*2abb3134SXin Li      read case_name _ <<< $line  # extract the first token
307*2abb3134SXin Li      echo $case_name $i $impl
308*2abb3134SXin Li    done
309*2abb3134SXin Li  done
310*2abb3134SXin Li}
311*2abb3134SXin Li
312*2abb3134SXin Li# Print the default number of parallel processes, which is max(#CPUs - 1, 1)
313*2abb3134SXin Lidefault-processes() {
314*2abb3134SXin Li  processors=$(grep -c ^processor /proc/cpuinfo || echo 4)  # Linux-specific
315*2abb3134SXin Li  if test $processors -gt 1; then  # leave one CPU for the OS
316*2abb3134SXin Li    processors=$(expr $processors - 1)
317*2abb3134SXin Li  fi
318*2abb3134SXin Li  echo $processors
319*2abb3134SXin Li}
320*2abb3134SXin Li
321*2abb3134SXin Li# Args:
322*2abb3134SXin Li#   spec_gen: A program to execute to generate the spec.
323*2abb3134SXin Li#   spec_regex: A pattern selecting the subset of tests to run
324*2abb3134SXin Li#   parallel: Whether the tests are run in parallel (T/F).  Sequential
325*2abb3134SXin Li#     runs log to the console; parallel runs log to files.
326*2abb3134SXin Li#   impl: one of python, or cpp
327*2abb3134SXin Li#   instances: A number of times each test case is run
328*2abb3134SXin Li
329*2abb3134SXin Li_run-tests() {
330*2abb3134SXin Li  local spec_gen=$1
331*2abb3134SXin Li  local spec_regex="$2"  # grep -E format on the spec, can be empty
332*2abb3134SXin Li  local parallel=$3
333*2abb3134SXin Li  local impl=${4:-"cpp"}
334*2abb3134SXin Li  local instances=${5:-1}
335*2abb3134SXin Li
336*2abb3134SXin Li  local regtest_dir=$REGTEST_BASE_DIR/$impl
337*2abb3134SXin Li  rm -r -f --verbose $regtest_dir
338*2abb3134SXin Li
339*2abb3134SXin Li  mkdir --verbose -p $regtest_dir
340*2abb3134SXin Li
341*2abb3134SXin Li  local func
342*2abb3134SXin Li  local processors
343*2abb3134SXin Li
344*2abb3134SXin Li  if test $parallel = F; then
345*2abb3134SXin Li    func=_run-one-instance  # output to the console
346*2abb3134SXin Li    processors=1
347*2abb3134SXin Li  else
348*2abb3134SXin Li    func=_run-one-instance-logged
349*2abb3134SXin Li    # Let the user override with MAX_PROC, in case they don't have enough
350*2abb3134SXin Li    # memory.
351*2abb3134SXin Li    processors=${MAX_PROC:-$(default-processes)}
352*2abb3134SXin Li    log "Running $processors parallel processes"
353*2abb3134SXin Li  fi
354*2abb3134SXin Li
355*2abb3134SXin Li  local cases_list=$regtest_dir/test-cases.txt
356*2abb3134SXin Li  # Need -- for regexes that start with -
357*2abb3134SXin Li  $spec_gen | grep -E -- "$spec_regex" > $cases_list
358*2abb3134SXin Li
359*2abb3134SXin Li  # Generate parameters for all test cases.
360*2abb3134SXin Li  cat $cases_list \
361*2abb3134SXin Li    | xargs -l -P $processors -- $0 _setup-one-case $impl \
362*2abb3134SXin Li    || test-error
363*2abb3134SXin Li
364*2abb3134SXin Li  log "Done generating parameters for all test cases"
365*2abb3134SXin Li
366*2abb3134SXin Li  local instances_list=$regtest_dir/test-instances.txt
367*2abb3134SXin Li  _setup-test-instances $instances $impl < $cases_list > $instances_list
368*2abb3134SXin Li
369*2abb3134SXin Li  cat $instances_list \
370*2abb3134SXin Li    | xargs -l -P $processors -- $0 $func || test-error
371*2abb3134SXin Li
372*2abb3134SXin Li  log "Done running all test instances"
373*2abb3134SXin Li
374*2abb3134SXin Li  make-summary $regtest_dir $impl
375*2abb3134SXin Li}
376*2abb3134SXin Li
377*2abb3134SXin Li# used for most tests
378*2abb3134SXin Lireadonly REGTEST_SPEC=tests/regtest_spec.py
379*2abb3134SXin Li
380*2abb3134SXin Li# Run tests sequentially.  NOTE: called by demo.sh.
381*2abb3134SXin Lirun-seq() {
382*2abb3134SXin Li  local spec_regex=${1:-'^r-'}  # grep -E format on the spec
383*2abb3134SXin Li  shift
384*2abb3134SXin Li
385*2abb3134SXin Li  time _run-tests $REGTEST_SPEC $spec_regex F $@
386*2abb3134SXin Li}
387*2abb3134SXin Li
388*2abb3134SXin Li# Run tests in parallel
389*2abb3134SXin Lirun() {
390*2abb3134SXin Li  local spec_regex=${1:-'^r-'}  # grep -E format on the spec
391*2abb3134SXin Li  shift
392*2abb3134SXin Li
393*2abb3134SXin Li  time _run-tests $REGTEST_SPEC $spec_regex T $@
394*2abb3134SXin Li}
395*2abb3134SXin Li
396*2abb3134SXin Li# Run tests in parallel (7+ minutes on 8 cores)
397*2abb3134SXin Lirun-all() {
398*2abb3134SXin Li  log "Running all tests. Can take a while."
399*2abb3134SXin Li  time _run-tests $REGTEST_SPEC '^r-' T cpp
400*2abb3134SXin Li}
401*2abb3134SXin Li
402*2abb3134SXin Lirun-user() {
403*2abb3134SXin Li  local spec_regex=${1:-}
404*2abb3134SXin Li  local parallel=T  # too much memory
405*2abb3134SXin Li  time _run-tests tests/user_spec.py "$spec_regex" $parallel cpp
406*2abb3134SXin Li}
407*2abb3134SXin Li
408*2abb3134SXin Li# Use stable true values
409*2abb3134SXin Licompare-python-cpp() {
410*2abb3134SXin Li  local num_unique_values=100
411*2abb3134SXin Li  local num_clients=10000
412*2abb3134SXin Li  local values_per_client=10
413*2abb3134SXin Li  local num_cohorts=64
414*2abb3134SXin Li
415*2abb3134SXin Li  local true_values=$REGTEST_BASE_DIR/stable_true_values.csv
416*2abb3134SXin Li
417*2abb3134SXin Li  tests/gen_true_values.R \
418*2abb3134SXin Li    exp $num_unique_values $num_clients $values_per_client $num_cohorts \
419*2abb3134SXin Li    $true_values
420*2abb3134SXin Li
421*2abb3134SXin Li  wc -l $true_values
422*2abb3134SXin Li
423*2abb3134SXin Li  # Run Python and C++ simulation on the same input
424*2abb3134SXin Li
425*2abb3134SXin Li  ./build.sh cpp-client
426*2abb3134SXin Li
427*2abb3134SXin Li  TRUE_VALUES_PATH=$true_values \
428*2abb3134SXin Li    ./regtest.sh run-seq '^demo3' 1 python
429*2abb3134SXin Li
430*2abb3134SXin Li  TRUE_VALUES_PATH=$true_values \
431*2abb3134SXin Li    ./regtest.sh run-seq '^demo3' 1 cpp
432*2abb3134SXin Li
433*2abb3134SXin Li  head _tmp/{python,cpp}/demo3/1/case_reports.csv
434*2abb3134SXin Li}
435*2abb3134SXin Li
436*2abb3134SXin Liif test $# -eq 0 ; then
437*2abb3134SXin Li  usage
438*2abb3134SXin Lielse
439*2abb3134SXin Li  "$@"
440*2abb3134SXin Lifi
441