diff --git a/.gitignore b/.gitignore index c04dd5653..80ca82579 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ larsoft-cfg *.dot configure +*.gz diff --git a/cfg/wirecell.jsonnet b/cfg/wirecell.jsonnet index 2a71d58b3..7adf97b08 100644 --- a/cfg/wirecell.jsonnet +++ b/cfg/wirecell.jsonnet @@ -334,6 +334,16 @@ // go through a format/parse. Maybe there's a better way? roundToInt(x):: std.parseInt("%d" % (x+0.5)), + // Like the shell command of the same name. + basename(name, ext="", delim="/") :: + local parts = std.split(name, delim); + local base = parts[std.length(parts)-1]; + if std.endsWith(base, ext) then + base[:std.length(base)-std.length(ext)] + else + base, + + freqbinner :: function(tick, nsamples) { nyquist : 0.5 / tick, hz_perbin : 1.0/(tick/$.second * nsamples), diff --git a/gen/src/AddNoise.cxx b/gen/src/AddNoise.cxx index ea8b097a0..0c66932f6 100644 --- a/gen/src/AddNoise.cxx +++ b/gen/src/AddNoise.cxx @@ -5,6 +5,7 @@ #include "WireCellAux/SimpleTrace.h" #include "WireCellAux/SimpleFrame.h" +#include "WireCellAux/FrameTools.h" #include @@ -116,6 +117,10 @@ bool Gen::IncoherentAddNoise::operator()(const input_pointer& inframe, output_po outframe = make_shared(inframe->ident(), inframe->time(), outtraces, inframe->tick()); log->debug("call={} frame={} {} traces", m_count, inframe->ident(), outtraces.size()); + + log->debug("input : {}", Aux::taginfo(inframe)); + log->debug("output: {}", Aux::taginfo(outframe)); + ++m_count; return true; } @@ -194,10 +199,11 @@ bool Gen::CoherentAddNoise::operator()(const input_pointer& inframe, output_poin outtraces.push_back(trace); } outframe = make_shared(inframe->ident(), inframe->time(), outtraces, inframe->tick()); - log->debug("call={} frame={} {} traces", - m_count, inframe->ident(), outtraces.size()); + + log->debug("input : {}", Aux::taginfo(inframe)); + log->debug("output: {}", Aux::taginfo(outframe)); + ++m_count; return true; - } diff --git a/gen/src/DepoTransform.cxx b/gen/src/DepoTransform.cxx index 14c29f480..629798f84 100644 --- a/gen/src/DepoTransform.cxx +++ b/gen/src/DepoTransform.cxx @@ -44,6 +44,7 @@ #include "WireCellAux/SimpleTrace.h" #include "WireCellAux/SimpleFrame.h" #include "WireCellAux/DepoTools.h" +#include "WireCellAux/FrameTools.h" #include "WireCellIface/IAnodePlane.h" @@ -205,8 +206,9 @@ bool Gen::DepoTransform::operator()(const input_pointer& in, output_pointer& out } auto frame = make_shared(m_frame_count, m_start_time, traces, m_tick); - log->debug("call={} frame={} ndepos_in={} ndepos_used={} ntraces={}", - m_count, m_frame_count, depos->size(), ndepos_used, traces.size()); + log->debug("call={} count={} ndepos_in={} ndepos_used={}", + m_count, m_frame_count, depos->size(), ndepos_used); + log->debug("output: {}", Aux::taginfo(frame)); ++m_frame_count; ++m_count; diff --git a/gen/src/Digitizer.cxx b/gen/src/Digitizer.cxx index 19011a602..4874a2b06 100644 --- a/gen/src/Digitizer.cxx +++ b/gen/src/Digitizer.cxx @@ -165,6 +165,10 @@ bool Gen::Digitizer::operator()(const input_pointer& vframe, output_pointer& adc log->debug("call={} traces={} frame={} totadc={} outtag=\"{}\"", m_count, adctraces.size(), vframe->ident(), totadc, m_frame_tag); + + log->debug("input : {}", Aux::taginfo(vframe)); + log->debug("output: {}", Aux::taginfo(adcframe)); + ++m_count; return true; } diff --git a/gen/src/Reframer.cxx b/gen/src/Reframer.cxx index b2a9e60ba..a0047ce86 100644 --- a/gen/src/Reframer.cxx +++ b/gen/src/Reframer.cxx @@ -3,6 +3,7 @@ #include "WireCellAux/SimpleFrame.h" #include "WireCellAux/SimpleTrace.h" +#include "WireCellAux/FrameTools.h" #include "WireCellUtil/NamedFactory.h" @@ -151,6 +152,9 @@ bool Gen::Reframer::operator()(const input_pointer& inframe, output_pointer& out outframe = sframe; + log->debug("input : {}", Aux::taginfo(inframe)); + log->debug("output: {}", Aux::taginfo(outframe)); + report << "out tag: \"" << m_frame_tag << "\""; log->debug(report.str()); diff --git a/gen/test/history-addnoise.bats b/gen/test/history-addnoise.bats deleted file mode 100644 index 6a26b0d0c..000000000 --- a/gen/test/history-addnoise.bats +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bats - -# This test consumes historical files produced by test-addnoise.bats -# -# See eg bv-generate-history-haiku for example how to automate -# producing historical files. O.w. collect one or more history/ -# directories and add their parent locations to WCTEST_DATA_PATH. - -bats_load_library wct-bats.sh - -# bats test_tags=time:1 - -@test "historical addnoise comp1d plots" { - - local wcplot=$(wcb_env_value WCPLOT) - - # will cd here to make plots have minimal filename labels - local rundir=$(blddir)/tests/history - # but will deposite plot files to our temp dir - local outdir=$(tmpdir) - - - local inpath="test-addnoise/test-addnoise-empno-6000.tar.gz" - local frame_files=( $(historical_files -v $(version) -l 2 $inpath) ) - # yell "frame files: ${frame_files[@]}" - frame_files=( $(realpath --relative-to=$rundir ${frame_files[*]}) ) - - cd $rundir - - for plot in wave spec - do - local plotfile="$outdir/comp1d-${plot}-history.png" - $wcplot \ - comp1d -n $plot --markers 'o + x .' -t '*' \ - --chmin 0 --chmax 800 -s --transform ac \ - -o $plotfile ${frame_files[*]} - saveout -c reports $plotfile - - done - - local plotfile="$outdir/comp1d-spec-history-zoom1.png" - $wcplot \ - comp1d -n spec --markers 'o + x .' -t '*' \ - --chmin 0 --chmax 800 -s --transform ac \ - --xrange 100 800 \ - -o $plotfile ${frame_files[*]} - saveout -c reports $plotfile - - local plotfile="$outdir/comp1d-spec-history-zoom2.png" - $wcplot \ - comp1d -n spec --markers 'o + x .' -t '*' \ - --chmin 0 --chmax 800 -s --transform ac \ - --xrange 1000 2000 \ - -o $plotfile ${frame_files[*]} - saveout -c reports $plotfile - - -} - diff --git a/gen/test/test-addnoise.bats b/gen/test/test-addnoise.bats index 8c3d802d9..38ca0b820 100644 --- a/gen/test/test-addnoise.bats +++ b/gen/test/test-addnoise.bats @@ -10,39 +10,58 @@ # cd ~/path/to/old/release # bats ~/path/to/new-wct/gen/test/test-addnoise.bats -# bats file_tags=noise,history +# bats file_tags=noise bats_load_library wct-bats.sh -# The intention is to run this test in multiple releases and compare across releases. -# bats test_tags=history,plots,implicit -@test "generate simple noise for comparison with older releases" { +log_file="wire-cell.log" +adc_file="frames-adc.tar.gz" +dag_file="dag.json" +setup_file () { cd_tmp local nsamples=6000 local noise=empno - local name="test-addnoise-${noise}-${nsamples}" - local adcfile="${name}.tar.gz" # format with support going back the longest - local cfgfile="${BATS_TEST_FILENAME%.bats}.jsonnet" - - run wire-cell -l "$logfile" -L debug \ - -A nsamples=$nsamples -A noise=$noise -A outfile="$adcfile" \ - -c "$cfgfile" - echo "$output" - [[ "$status" -eq 0 ]] - [[ -s "$adcfile" ]] - saveout -c history "$adcfile" + local cfg_file="${BATS_TEST_FILENAME%.bats}.jsonnet" + + run_idempotently -s $cfg_file -t $dag_file -- \ + compile_jsonnet $cfg_file $dag_file \ + -A nsamples=$nsamples -A noise=$noise -A outfile="$adc_file" + + run_idempotently -s $dag_file -t $adc_file -t $log_file -- \ + wct -l $log_file -L debug -c $dag_file + + + # run wire-cell -l "$logfile" -L debug \ + # -A nsamples=$nsamples -A noise=$noise -A outfile="$adc_file" \ + # -c "$cfgfile" +} + +# bats test_tags=history +@test "make history" { + cd_tmp file + [[ -s "$dag_file" ]] + [[ -s "$adc_file" ]] + saveout -c history "$dag_file" + saveout -c history "$adc_file" +} + +# The intention is to run this test in multiple releases and compare across releases. +# bats test_tags=plots,implicit +@test "generate simple noise for comparison with older releases" { + + cd_tmp file local wcplot=$(wcb_env_value WCPLOT) for what in spec wave do - local pout="${name}-comp1d-${what}.png" + local pout="comp1d-${what}.png" $wcplot comp1d \ -o $pout \ -t '*' -n $what \ --chmin 0 --chmax 800 -s --transform ac \ - "${adcfile}" + "${adc_file}" echo "$output" [[ "$status" -eq 0 ]] [[ -s "$pout" ]] diff --git a/img/test/test-wct-uboone-img.bats b/img/test/test-wct-uboone-img.bats index 3199a9c32..48ad981ba 100644 --- a/img/test/test-wct-uboone-img.bats +++ b/img/test/test-wct-uboone-img.bats @@ -2,114 +2,66 @@ # Run tests related to applying imaging to a rich uboone event. -#load ../../test/wct-bats.sh -# load "/home/bv/wrk/wct/check-test/toolkit/test/wct-bats.sh" -# set BATS_LIB_PATH externally bats_load_library "wct-bats.sh" -base_name () { - local fmt="${1:-json}" - echo "test-wct-uboone-img-${fmt}" -} - -run_with_fmt () { - local fmt="${1:-json}" - - # was: uboone/celltree/celltreeOVERLAY-event6501.tar.bz2 - local infile="$(input_file frames/celltreeOVERLAY-event6501.tar.bz2)" - [[ -s "$infile" ]] +dag_file="dag.json" +log_file="wire-cell.log" +img_numpy_file="clusters-numpy.tar.gz" +img_json_file="clusters-json.tar.gz" - local cfgfile="$(relative_path test-wct-uboone-img.jsonnet)" - [[ -s "$cfgfile" ]] - - - local base=$(base_name $fmt) - local jcfgfile="${base}-dag.json" - local outfile="${base}-clusters.tar.gz" - - if [ -f "$outfile" ] ; then - echo "reusing $outfile" 1>&3 - return - fi - - compile_jsonnet "$cfgfile" "$jcfgfile" -A fmt=$fmt -A infile="$infile" -A outfile="$outfile" - echo "$output" - [[ "$status" -eq 0 ]] - [[ -s "$jcfgfile" ]] - - local log="${base}.log" - rm -f $log - wct -L debug -l $log "$jcfgfile" - echo "$output" - [[ "$status" -eq 0 ]] -} +setup_file () { + skip_if_no_input -run_both_formats () { + cd_tmp file # was: uboone/celltree/celltreeOVERLAY-event6501.tar.bz2 local infile="$(input_file frames/celltreeOVERLAY-event6501.tar.bz2)" [[ -s "$infile" ]] - local cfgfile="$(relative_path test-wct-uboone-img.jsonnet)" - [[ -s "$cfgfile" ]] - - - local jcfgfile="$(base_name dag).json" - local jout="$(base_name json).tar.gz" - local nout="$(base_name numpy).tar.gz" - - if [ -f "$jout" -a -f "$nout" ] ; then - echo "reusing wire-cell output" 1>&3 - return - fi + local cfg_file="$(relative_path test-wct-uboone-img.jsonnet)" + [[ -s "$cfg_file" ]] - compile_jsonnet "$cfgfile" "$jcfgfile" -A infile="$infile" -A outpat="$(base_name '%s').tar.gz" - echo "$output" - [[ "$status" -eq 0 ]] - [[ -s "$jcfgfile" ]] + run_idempotently -s "$cfg_file" -t "$dag_file" -- \ + compile_jsonnet "$cfg_file" "$dag_file" \ + -A infile="$infile" -A outpat="clusters-%s.tar.gz" -A formats="json,numpy" + [[ -s "$dag_file" ]] - local log="$(base_name "log").txt" - local err="$(base_name "err").txt" - rm -f $log - # wct -L debug -l $log "$jcfgfile" - echo wire-cell -L debug -l $log "$jcfgfile" - wire-cell -L debug -l $log "$jcfgfile" > $err 2>&1 - echo "$output" - [[ "$status" -eq 0 ]] + run_idempotently -s "$dag_file" -s "$infile" \ + -t "$img_numpy_file" -t "$img_json_file" -t "$log_file" -- \ + wct -l "$log_file" -L debug -c "$dag_file" + [[ -s "$log_file" ]] } -setup_file () { - skip_if_no_input +# bats test_tags=history +@test "make history" { cd_tmp file - # run_with_fmt json - # run_with_fmt numpy - run_both_formats + [[ -s "$img_numpy_file" ]] + [[ -s "$img_json_file" ]] + saveout -c history "$img_numpy_file" + saveout -c history "$img_json_file" } -@test "create graph" { +# bats test_tags=dotify +@test "dotify dag" { cd_tmp file - # only bother with default format (json) - local base="$(base_name dag)" - dotify_graph "${base}.json" "${base}.svg" - saveout -c plots "${base}.svg" + dotify_graph "$dag_file" "dag.svg" + saveout -c plots "dag.svg" } -@test "check log files" { +@test "check wire-cell log file" { cd_tmp file - local log="$(base_name "log").txt" - - local errors="$(egrep ' W | E ' $log)" + local errors="$(egrep ' W | E ' $log_file)" echo "$errors" [[ -z "$errors" ]] - } - -@test "inspect blobs" { +function do_blobs () { + local what="$1"; shift + local args=( $@ ) cd_tmp file @@ -118,14 +70,12 @@ setup_file () { local wcimg=$(wcb_env_value WCIMG) for fmt in json numpy do - local base="$(base_name $fmt)" - - local log="${base}.inspect" + local log="${what}-${fmt}.log" logs[$fmt]="$log" - local dat="${base}.tar.gz" - echo "$wcimg inspect --verbose -o $log $dat" - run $wcimg inspect --verbose -o "$log" "$dat" + local dat="clusters-${fmt}.tar.gz" + echo $wcimg $what "${args[@]}" -o "$log" "$dat" + run $wcimg $what "${args[@]}" -o "$log" "$dat" echo "$output" [[ "$status" -eq 0 ]] [[ -s "$log" ]] @@ -133,36 +83,29 @@ setup_file () { local delta="$(diff -u ${logs[*]})" [[ -z "$delta" ]] - } - +@test "inspect blobs" { + do_blobs inspect --verbose +} @test "dump blobs" { - - cd_tmp file - - local wcimg=$(wcb_env_value WCIMG) - for fmt in json numpy - do - local base="$(base_name $fmt)" - echo $wcimg dump-blobs -o ${base}.dump ${base}.tar.gz - run $wcimg dump-blobs -o ${base}.dump ${base}.tar.gz - echo "$output" - [[ "$status" -eq 0 ]] - [[ -s "${base}.dump" ]] - done - - run diff -u $(base_name json).dump $(base_name numpy).dump - echo "$output" - [[ "$status" -eq 0 ]] - [[ -z "$delta" ]] + do_blobs dump-blobs } + @test "at least one multi-blob measure" { - local fname="$(base_name numpy).inspect" - echo $fname + cd_tmp file - local got=$(grep 'nn for m' "${fname}" | grep -v 'b=1\b') + local got=$(grep 'nn for m' inspect-numpy.log | grep -v 'b=1\b') echo "$got" [[ -n "$got" ]] } + +# bats test_tags=plots +@test "plot blobs" { + cd_tmp file + + local wcimg=$(wcb_env_value WCIMG) + run $wcimg plot-blobs --single --plot views clusters-numpy.tar.gz blob-views.png + +} diff --git a/img/test/test-wct-uboone-img.jsonnet b/img/test/test-wct-uboone-img.jsonnet index c44cb9e45..0c6356899 100644 --- a/img/test/test-wct-uboone-img.jsonnet +++ b/img/test/test-wct-uboone-img.jsonnet @@ -341,7 +341,7 @@ local masked_planes = [[],[2],[0],[1]]; function(infile="celltreeOVERLAY-event6501.tar.bz2", outpat="clusters-event6501-%s.tar.gz", slicing = "single", - fmt = "json") + formats = "json,numpy") local multi_slicing = slicing; local imgpipe = @@ -384,7 +384,7 @@ local graph = pg.pipeline([ // magdecon, // magnify out // dumpframes, imgpipe, - pg.fan.fanout("ClusterFanout", [img.dump(outpat%"json", "json"),img.dump(outpat%"numpy", "numpy")], "") + pg.fan.fanout("ClusterFanout", [img.dump(outpat%fmt, fmt) for fmt in std.split(formats, ',')], "") // img.dump(outfile, fmt), ], "main"); diff --git a/sigproc/src/OmnibusNoiseFilter.cxx b/sigproc/src/OmnibusNoiseFilter.cxx index 34499a223..5f94cb37f 100644 --- a/sigproc/src/OmnibusNoiseFilter.cxx +++ b/sigproc/src/OmnibusNoiseFilter.cxx @@ -102,6 +102,7 @@ bool OmnibusNoiseFilter::operator()(const input_pointer& inframe, output_pointer ++m_count; return true; } + log->debug("call={} input frame: {}", m_count, Aux::taginfo(inframe)); auto traces = Aux::tagged_traces(inframe, m_intag); if (traces.empty()) { @@ -245,6 +246,7 @@ bool OmnibusNoiseFilter::operator()(const input_pointer& inframe, output_pointer sframe->tag_frame("noisefilter"); outframe = IFrame::pointer(sframe); + log->debug("call={} output frame: {}", m_count, Aux::taginfo(outframe)); log->debug("call={}, frame={}, ntraces={}, nticks={} intag={} outtag={}", m_count, sframe->ident(), itraces.size(), m_nticks, diff --git a/sigproc/src/OmnibusSigProc.cxx b/sigproc/src/OmnibusSigProc.cxx index 4d0f9109b..543e10117 100644 --- a/sigproc/src/OmnibusSigProc.cxx +++ b/sigproc/src/OmnibusSigProc.cxx @@ -7,6 +7,7 @@ #include "WireCellAux/SimpleFrame.h" #include "WireCellAux/SimpleTrace.h" +#include "WireCellAux/FrameTools.h" #include "WireCellIface/IFieldResponse.h" #include "WireCellIface/IFilterWaveform.h" @@ -1444,6 +1445,8 @@ bool OmnibusSigProc::operator()(const input_pointer& in, output_pointer& out) ++m_count; return true; } + log->debug("call={} input frame: {}", m_count, Aux::taginfo(in)); + const size_t ntraces = in->traces()->size(); if (!ntraces) { out = std::make_shared(in->ident(), in->time(), std::make_shared(), in->tick()); @@ -1707,6 +1710,9 @@ bool OmnibusSigProc::operator()(const input_pointer& in, output_pointer& out) m_frame_tag); out = IFrame::pointer(sframe); + + log->debug("call={} output frame: {}", m_count, Aux::taginfo(in)); + ++m_count; return true; diff --git a/sigproc/src/Protodune.cxx b/sigproc/src/Protodune.cxx index 72bc8d059..fc9d36529 100644 --- a/sigproc/src/Protodune.cxx +++ b/sigproc/src/Protodune.cxx @@ -772,7 +772,9 @@ WireCell::Waveform::ChannelMaskMap Protodune::OneChannelNoise::apply(int ch, sig if (!is_partial) { auto const& spec = m_noisedb->rcrc(ch); // rc_layers set to 1 in channel noise db - WireCell::Waveform::shrink(spectrum, spec); + if (spec.size() == spectrum.size()) { + WireCell::Waveform::shrink(spectrum, spec); + } } // remove the "50kHz" noise in some collection channels diff --git a/test/README.org b/test/README.org index a4c1aaa0a..0bdc8e7d4 100644 --- a/test/README.org +++ b/test/README.org @@ -1,5 +1,6 @@ #+title: Wire-Cell Toolkit Testing #+SETUPFILE: ../setup-readme.org +#+options: broken-links:t The Wire-Cell Toolkit (WCT) includes a large number of tests. Developers and users are strongly encouraged to contribute even more. diff --git a/test/docs/bats.org b/test/docs/bats.org index 4d5ec1a1f..03eaf9033 100644 --- a/test/docs/bats.org +++ b/test/docs/bats.org @@ -15,7 +15,7 @@ With very little additional effort compared to plain shell scripts, a BATS test - stdout/stderr management. - simple ways to run commands and check for success. -* BATS in WCT +** BATS in WCT WCT provides a copy of BATS as the version coming with many operating systems is not up to date. It will be used by the build system. When running BATS tests directly the user should assure: @@ -24,7 +24,7 @@ WCT provides a copy of BATS as the version coming with many operating systems is $ export BATS_LIB_PATH=/path/to/my/wire-cell-toolkit/test #+end_example -* My first BATS test +** My first BATS test #+begin_example bats_load_library wct-bats.sh @@ -47,7 +47,7 @@ WCT provides a copy of BATS as the version coming with many operating systems is The command ~wct~ is actually a function from ~wct-bats.sh~ that runs ~wire-cell~ and instruments some default checking. The ~wcsonnet~ is not so wrapped but is available as a ~wcb~ variable. The ~run~ command is a BATS helper to fill ~$output~ and ~$status~, which we check. -* Running BATS tests +** Running BATS tests A BATS test should run from anywhere: @@ -60,7 +60,7 @@ As in this example, all ~@test~ functions in a BATS file will be executed sequen bats -f "wire-cell" test/test/test_my_first_bats.bats #+end_example -* Creating a BATS test +The* Creating a BATS test Here we give some detailed guidance on making a BATS test. @@ -136,9 +136,7 @@ function teardown_file () { #+end_example One example for using ~setup_file~ is to run any long-running programs that produce output required by more than one ~@test~. -** Input and output files - -*** Temporary files +** Temporary files BATS has a concept of a context-dependent temporary working directory. The contexts are: @@ -170,15 +168,22 @@ Alternatively, ~wct-bats.sh~ overrules default temporary directories, combines $ WCTEST_TMPDIR=$HOME/my-wct-tmp-dir bats [...] #+end_example - -*** Persistent files +** Persistent files Some BATS tests may use or create files that persist beyond the temporary context via the WCT test data repository (see section [[Data repository]]). The ~wct-bats.org~ library provides some functions to help work with such files. -For a test that produces historical files, they may be saved to the "history" category of the repo with: +For a test that produces *historical files*, they may be saved to the "history" category of the repo with: #+begin_example -saveout -c history my-file-for-history.npz + # bats test_tags=history + @test "make history" { + # ... + saveout -c history my-file-for-history.npz + } #+end_example +#+begin_note +Only place the ~history~ tag on tests that save history files. History can then be quickly refreshed by running ~bats --filter-tags history */test/test*.bats~ and this command can be run in a number of software build environments to refresh past history after some new historical tests are added. +#+end_note + A known input file may be resolved as: @@ -208,47 +213,56 @@ Likewise, but just the version strings local myhistvers_released=( category_versions ) #+end_example +** Idempotent running -*** Idempotent tests - -Some tests are long running and multi stage. As just described, a test should run in a per-test temporary directory by calling ~cd_tmp~. When developing tests or investigating failures, it can be very painful to run and re-run the same test many times. By setting ~WCTEST_TMPDIR~ we can control where the test runs, but we must still write the tests to be *idempotent* to allow us to quickly run and re-run them and not repeat the portions that pass. - -Adding idempotency to tests simply comes down to adding test for command output files and only running the command if the file is missing. An example: +The ~wct-bats.sh~ BATS library provides a helper function to run a particular test command in an *idempotent* manner. The function is called like: #+begin_example - @test "an idempotent test" { - myout="myoutput.txt" - if [ -f "$myout" ] ; then - echo "reusing $myout" - else - date > $myout - fi - # ... - } +run_idempotently [sources] [targets] -- #+end_example -Here the ~date~ command stands in for a "long running" program. The ~echo~ is not seen on the terminal unless the test later fails. That's it. Now when running and re-running the test with ~WCTEST_TMPDIR~ set it will only call the "slow" ~date~ program once. -Another common pattern is one initial, long-running command followed by many faster tests that utilize the results of that first command. This pattern is well served by using the Bats ~setup_file~ function to run the command in the temporary directory at "file scope" via ~cd_tmp file~. As default scope is per-test, each subsequent test must locate that file-scope temp. +Where one or more sources are specified with ~-s|--source ~ and one or more targets with ~-t|--target ~ options. The ~~ will only be executed if: +- No source or no target given. +- Any target files are missing. +- Any target file is older than any source file. + +When any of these conditions are not met, the ~run_idempotently~ will simply announce (~yell~) that it is not running the command line and immediately return. + +Otherwise, the command line is run and the ~$status~ code is checked before returning. + +Thus, when the developer runs and re-runs the BATS test with ~WCTEST_TMPDIR~ set to a fixed directory the ~~ will only be re-run when needed. + +While this will not speed up normal testing, it can dramatically speed up re-running the test by a developer. This can help during development of the test itself, developing code that is being tested and investigating test failures. This development pattern is also helped with ~bats -f ~ and use of ~setup_file~ as described next. + +** Using ~setup_file~ + +Another method to run tests in an idempotent manner is to place common, perhaps long running, tasks in the ~setup_file~ function, run the entire test with ~WCTEST_TMPDIR~ set and then re-run specific tests with ~bats -f ~. When a specific test is exercising some issue, this lets the developer focus on just that issue and reuse prior results. Consider the example: #+begin_example - function setup_file -o output.dat () { + function setup_file () { cd_tmp file - run my_slow_command + run my_slow_command -o output1.txt [[ "$status" -eq 0 ]] } - @test "A test running in file temp dir" { + @test "Some test for number one" { cd_tmp file - [[ -s output.dat ]] + run test_some_test1 output1.txt } - @test "A in test temp dir using file temp dir" { - local ft="$(tmpdir file)" - cd_tmp - [[ -s "$ft/output.dat" ]] + @test "Some test for number two" { + cd_tmp file + run test_some_test2 output1.txt } #+end_example +Then the developer may do something like: +#+begin_example + $ WCTEST_TEMPDIR=/tmp/my-test bats my-test.bats + $ WCTEST_TEMPDIR=/tmp/my-test bats -f one my-test.bats +#+end_example + +To force a full re-run simply remove the ~/tmp/my-test~ and perhaps run after unseting ~WCTEST_TMPDIR~. ** Test tags @@ -261,13 +275,19 @@ As shown in the [[First steps]] one can assert [[https://bats-core.readthedocs.i @test "test noise spectra for issue 202" { ... } +# bats test_tags=history +@test "make history" { + ... + saveout -c history somefile.npz +} + #+end_example Tag name conventions are defined here: - ~implicit~ :: The test only performs implicit tests ("it ran and didn't crash") and side effects (report, history). - ~report~ :: The test produces a "report" of files saved to output (see [[Reports]]) -- ~history~ :: The test produces results relevant to multiple released versions (see [[Historical tests]]). +- ~history~ :: The test produces results relevant to multiple released versions (see [[Historical tests]]). *Only place this tag on tests that produce history files* - ~issue:~ :: The test is relevant to GitHub issue of the given number. - ~pkg:~ :: The test is part of package named ~~ (~gen~, ~util~, etc) - ~topic:~ :: The test relates to topic named ~~ (~wires~, ~response~, etc) @@ -279,7 +299,6 @@ bats --filter-tags 'topic:wires,!time:3' util/test/test*.bats #+end_example See also the ~wcb --test-duration=~ options described in section [[Framework]]. - ** Test logging BATS uses the [[https://testanything.org/]["test anything protocol"]] to combine multiple tests in a coherent way. We need not be overly concerned with the details but it does mean that BATS captures ~stdout~ and ~stderr~ from the individual tests. When the user wishes to see diagnostic messages directly this causes annoyance. But, no worry as there are three mechanisms to emit and view such user diagonstics. @@ -324,9 +343,3 @@ Output to the special file descriptor ~3~ will always lead to that output to the Please avoid using this except in special, temporary cases, as it leads to very "noisy" tests. -** Reports - -Tests may produce reports in the form of PDF or web (HTML, PNG, JPG, etc). - - - diff --git a/test/docs/datarepo.org b/test/docs/datarepo.org index a2b04b376..0aac3cd5b 100644 --- a/test/docs/datarepo.org +++ b/test/docs/datarepo.org @@ -16,13 +16,14 @@ Developers of tests that will add to the repository must assure: ** Naming convention -With the exception of the "input" category, all files are located in -the repository with a path pattern matching a file "category", a -software version string and the source file name (see section [[Test -source]]): +The repository is composed of a files housed in directories named according to the following pattern: + #+begin_example /// #+end_example +The ~~ is the test name as described more in section [[Test source]]. + +One exception to this pattern is the "input" category. This and other categories as well as other details are described in the remaining sub sections. *** Categories @@ -79,16 +80,11 @@ Tests may add files to the test data repo following these guidelines: Special care should be given to the ~input~ and ~history~ categories. -The repo is *not* meant as a replacement for saving out otherwise -ephemeral files from a temporary directory. If such files are needed, -a user may always re-run the test with the temporary directory -retained. +The repo is *not* meant as a replacement for saving out otherwise ephemeral files from a temporary directory. If such files are needed, a user may always re-run the test with the temporary directory retained. ** Working directory -When a repo is in used in the context of a WCT software build it is -simply a directory under the ~build/~ directory. The ~input~ and other -categories are fund matching these paths: +When a repo is in used in the context of a WCT software build it is simply a directory under the ~build/~ directory. The ~input~ and other categories are fund matching these paths: #+begin_example build/tests/input/ @@ -97,16 +93,13 @@ build/tests//// ** Preparing a repo -The working directory is prepared as part of the normal build when -tests are enabled. This requires HTTP access. +The working directory is prepared as part of the normal build when tests are enabled. This requires HTTP access to the repo server. #+begin_example -waf +waf --tests #+end_example -Each release of WCT has a hard-wired list of past releases for which -the current release can use historical files. Normally, users need -not set this but if required this list may be overridden: +Each release of WCT has a hard-wired list of past releases for which the current release can use historical files. Normally, users need not set this but if required this list may be overridden: #+begin_example waf configure --tests --test-data-releases 0.23.0,0.24.1 [...] @@ -115,8 +108,7 @@ waf ** Distributing repository contents -Archive files for all history versions present in the working -directory may be produced. +Archive files for all history versions present in the working directory may be produced. #+begin_example waf packrepo @@ -128,10 +120,7 @@ Or, archives for specific releases may be produced with: waf packrepo --test-data-releases 0.20.0,0.21.0,0.22.0,0.23.0,0.24.1 #+end_example -Normal users need not perform this and experts may perform this as -part of the release. To get the correct version path, the local -working repo and the packing should be run in a clean, release -checkout. Use ~wire-cell --version~ to check what you will get. +Normal users need not perform this and experts may perform this as part of the release. To get the correct version path, the local working repo and the packing should be run in a clean, release checkout. Use ~wire-cell --version~ to check what you will get. ** Reinventing history @@ -144,4 +133,4 @@ It is expected that new historical tests will be developed to consume historical These steps can be performed manually by checking out the required code version, building and running tests as usual followed by explicitly running the new test in the new version but in the environment of the old version. -An example of automating this procedure can be found in ~test/scriptsbv-generate-history-haiku~. +An example of automating this procedure can be found in ~test/scripts/bv-generate-history-haiku~. diff --git a/test/docs/history.org b/test/docs/history.org index 5d9b19974..683d43ca0 100644 --- a/test/docs/history.org +++ b/test/docs/history.org @@ -30,4 +30,4 @@ Historical tests should only assume versions in this list but may also include t ** Support -See section [[BATS]]]] for description of support for historical tests written as BATS files. +See section [[BATS]] for description of support for historical tests written as BATS files. diff --git a/test/docs/languages.org b/test/docs/languages.org index e98f0daf6..44ff5f818 100644 --- a/test/docs/languages.org +++ b/test/docs/languages.org @@ -12,9 +12,9 @@ fast rules but the following guidelines may be considered: It is also possible to write WCT tests in Python and plain shell. However, these are not well supported nor currently recommended. However, developers are encouraged to consider adding functionality that is useful for tests into the various command Python modules and Click line interfaces in ~wire-cell-python~. These may then be easily used from a BATS test. #+end_note -The remainder of this sections gives details of WCT testing framework support specific to the recommended languages. +The remainder of this document gives details of WCT testing framework support specific to the recommended languages. -#+include: cpp.org -#+include: bats.org -#+include: jsonnet.org +#+include: cpp.org :minlevel 1 +#+include: bats.org :minlevel 1 +#+include: jsonnet.org :minlevel 1 diff --git a/test/docs/testing.org b/test/docs/testing.org index de9b37598..b0d8ceb2f 100644 --- a/test/docs/testing.org +++ b/test/docs/testing.org @@ -1,7 +1,7 @@ #+include: framework.org #+include: writing.org -#+include: languages.org #+include: datarepo.org #+include: history.org +#+include: languages.org diff --git a/test/scripts/bv-generate-history-haiku b/test/scripts/bv-generate-history-haiku deleted file mode 100755 index 1474d2398..000000000 --- a/test/scripts/bv-generate-history-haiku +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# -# This test generates and collects historical test files. -# -# It is named so as it assumes to run on my (bv) account on my -# workstation named "haiku". - -SHELL=bash - -# Base path to each release, distinguished only by the rel number, and -# each configured via direnv. -release_base="$HOME/wrk/wct/rel" - -scrdir="$(realpath $(dirname $BASH_SOURCE))" -topdir="$(realpath ${scrdir}/../..)" -curver="$(wire-cell --version)" - -declare -a rels -for rel in $@ 20 21 22 23 24 -do - rels+=("${release_base}${rel}/toolkit") -done -rels+=("$topdir") - - -function direnv_cd() { - cd "$1" - eval "$(direnv export bash 2>/dev/null)" -} - -function mrun () { - for relpath in ${rels[*]} - do - direnv_cd "$relpath" - $@ - done -} - - -mrun wire-cell --version -# mrun ./wcb install --notests -p -mrun $topdir/test/bats/bin/bats $topdir/gen/test/test-addnoise.bats diff --git a/test/scripts/multi-release-testing b/test/scripts/multi-release-testing new file mode 100755 index 000000000..9ac050be7 --- /dev/null +++ b/test/scripts/multi-release-testing @@ -0,0 +1,101 @@ +#!/bin/bash + +usage () { + cat <&2 + if [[ $ver > 0.20 ]] ; then + rels+=( $rel ) + files+=( $topdir/gen/test/test-addnoise.bats ) + echo "ADDING test-addnoise.bats" 1>&2 + rels+=( $rel ) + files+=( $topdir/test/test/test-pdsp-simsn-nfsp.bats ) + echo "ADDING test-pdsp-simsn.bats" 1>&2 + fi + if [[ $ver > 0.24.1 ]] ; then + rels+=( $rel ) + files+=( $topdir/img/test/test-wct-uboone-img.bats ) + echo "ADDING test-wct-uboone-img.bats" 1>&2 + fi +done + + +# Starting with 0.25: +# history_tests+=( img/test/test-wct-uboone-img.bats ) + +# echo -- \ +parallel --link \ + $faker $scrdir/run-in-direnv {1} $topdir/test/bats/bin/bats ${tag_args[@]} {2} \ + ::: ${rels[@]} \ + ::: ${files[@]} + + + + + diff --git a/test/scripts/run-in-direnv b/test/scripts/run-in-direnv new file mode 100755 index 000000000..b7a008712 --- /dev/null +++ b/test/scripts/run-in-direnv @@ -0,0 +1,20 @@ +#!/bin/bash +wd=$1 ; shift + +scrdir="$(realpath $(dirname $BASH_SOURCE))" +topdir="$(realpath ${scrdir}/../..)" + +cd $wd +eval "$(direnv export bash 2>/dev/null)" + +# Must re-assert this as it may be set by .envrc +export BATS_LIB_PATH="$topdir/test" +if [ ! -f $BATS_LIB_PATH/wct-bats.sh ] ; then + echo "no BATS_LIB_PATH/wct-bats.sh" 1>&2 + exit -1 +fi + +# echo "DIRENV: $wd" 1>&2 +# echo "RUNNING: $@" 1>&2 + +"$@" diff --git a/test/test/history-comp1d.bats b/test/test/history-comp1d.bats new file mode 100644 index 000000000..1457586a7 --- /dev/null +++ b/test/test/history-comp1d.bats @@ -0,0 +1,69 @@ +#!/usr/bin/env bats + +# Make "comp1d" plots over recent history. + +bats_load_library wct-bats.sh + +# bats file_tags=report + +function comp1d () { + + local src=$1; shift + local infile="$1"; shift + local kind=$1; shift + local iplane=$1; shift + + local past=( $(historical_files --current -l 3 $infile) ) + + + local letters=("u" "v" "w") + local chmins=(0 800 1600) + local chmaxs=(800 1600 2560) + local letter=${letters[$iplane]} + local fig="${src}-${kind}-${letter}.png" + + declare -a args=( -s -n "$kind" --transform ac ) + args+=( --chmin ${chmins[$iplane]} --chmax ${chmaxs[$iplane]} ) + if [ "$src" = noise ] ; then + args+=( --tier '*' ) + else + args+=( --tier 'orig' ) + fi + args+=( -o "$fig" ) + + yell "ARGS: ${args[@]}" + + for one in ${past[@]} + do + deps+=( -s "$one" ) + done + + local wcplot=$(wcb_env_value WCPLOT) + run_idempotently ${deps[@]} -t "$fig" -- $wcplot comp1d "${args[@]}" ${past[@]} + [[ -s "$fig" ]] + saveout -c reports "$fig" +} + +@test "pdsp comp1d" { + cd_tmp + + # The list of files that can be comp1d'ed. These must be PDSP APA0 + declare -A inputs + inputs[noise]="test-addnoise/test-addnoise-empno-6000.tar.gz" + inputs[simsn]="test-pdsp-simsn/frames-adc.tar.gz" + + yell "INPUT KEYS: ${!inputs[@]}" + yell "INPUT VALS: ${inputs[@]}" + + for src in "${!inputs[@]}" + do + local infile="${inputs[$src]}" + for kind in wave spec + do + for ipln in 0 1 2 + do + comp1d $src $infile $kind $ipln + done + done + done +} diff --git a/test/test/pdsp-depos-simsn-frames.jsonnet b/test/test/pdsp-depos-simsn-frames.jsonnet deleted file mode 100644 index 902d0f982..000000000 --- a/test/test/pdsp-depos-simsn-frames.jsonnet +++ /dev/null @@ -1,123 +0,0 @@ -local g = import 'pgraph.jsonnet'; -local f = import 'pgrapher/common/funcs.jsonnet'; -local wc = import 'wirecell.jsonnet'; -local io = import 'pgrapher/common/fileio.jsonnet'; - -local tools_maker = import 'pgrapher/common/tools.jsonnet'; -local base = import 'pgrapher/experiment/pdsp/simparams.jsonnet'; -local params = base { - lar: super.lar { - // Longitudinal diffusion constant - // DL: std.extVar('DL') * wc.cm2 / wc.s, - DL: 4.0 * wc.cm2 / wc.s, - // Transverse diffusion constant - // DT: std.extVar('DT') * wc.cm2 / wc.s, - DT: 8.8 * wc.cm2 / wc.s, - // Electron lifetime - // lifetime: std.extVar('lifetime') * wc.ms, - lifetime: 10.4 * wc.ms, - // Electron drift speed, assumes a certain applied E-field - // drift_speed: std.extVar('driftSpeed') * wc.mm / wc.us, - drift_speed: 1.565 * wc.mm / wc.us, - }, -}; - -local tools_all = tools_maker(params); -local tools = tools_all { - anodes: [tools_all.anodes[0]], - // dft : { - // type: "cuFftDFT", - // }, -}; - -local sim_maker = import 'pgrapher/experiment/pdsp/sim.jsonnet'; -local sim = sim_maker(params, tools); - -local nanodes = std.length(tools.anodes); -local anode_iota = std.range(0, nanodes - 1); - -local bagger = sim.make_bagger(); - -local drifter = sim.drifter; -local setdrifter = g.pnode({ - type: 'DepoSetDrifter', - data: { - drifter: "Drifter" - } - }, nin=1, nout=1, - uses=[drifter]); - -// signal plus noise pipelines -//local sn_pipes = sim.signal_pipelines; -local sn_pipes = sim.splusn_pipelines; - -local magoutput = 'protodune-sim-check-wct.root'; -local magnify = import 'pgrapher/experiment/pdsp/magnify-sinks.jsonnet'; -local sinks = magnify(tools, magoutput); - -local sio_sinks(output) = g.pnode({ - type: "FrameFileSink", - data: { - outname: output, // "frames.tar.bz2", - tags: ["orig"], - digitize: false, - }, - }, nin=1, nout=0); - -local multipass = [ - g.pipeline([ - sn_pipes[n], - ], 'multipass%d' % n) - for n in anode_iota -]; -local outtags = ['orig%d' % n for n in anode_iota]; -local bi_manifold = f.fanpipe('DepoSetFanout', multipass, 'FrameFanin', 'sn_mag_nf', outtags); - -local retagger = g.pnode({ - type: 'Retagger', - data: { - // Note: retagger keeps tag_rules an array to be like frame fanin/fanout. - tag_rules: [{ - // Retagger also handles "frame" and "trace" like fanin/fanout - // merge separately all traces like gaussN to gauss. - frame: { - '.*': 'orig', - }, - merge: { - 'orig\\d': 'daq', - }, - }], - }, -}, nin=1, nout=1); - -local sink = sim.frame_sink; - - -local depo_source(input) = g.pnode({ - type: 'DepoFileSource', - data: { inname: input } // "depos.tar.bz2" -}, nin=0, nout=1); - -local make_graph(input,output) = - g.pipeline([depo_source(input), setdrifter, bi_manifold, retagger, sio_sinks(output)]); -local plugins = [ "WireCellSio", "WireCellGen", "WireCellSigProc","WireCellApps", "WireCellPgraph", "WireCellTbb", "WireCellRoot"]; - -function(input, output) - local graph = make_graph(input, output); - // Pgrapher or TbbFlow - local engine = "Pgrapher"; - local app = { - type: 'Pgrapher', - data: { - edges: g.edges(graph), - }, - }; - local cmdline = { - type: "wire-cell", - data: { - plugins: plugins, - apps: ["Pgrapher"], - } - }; - // Finally, the configuration sequence which is emitted. - [cmdline] + g.uses(graph) + [app] diff --git a/test/test/test-pdsp-simsn-nfsp.bats b/test/test/test-pdsp-simsn-nfsp.bats new file mode 100644 index 000000000..ebe32246f --- /dev/null +++ b/test/test/test-pdsp-simsn-nfsp.bats @@ -0,0 +1,165 @@ +#!/usr/bin/env bats + +# Run signal and noise simulation on PDSP APA0. + +# Produces plots and history files +# bats file_tags=plots + +# Started for https://github.com/WireCell/wire-cell-toolkit/pull/195 + +bats_load_library wct-bats.sh + +log_file="wire-cell.log" +dag_file="dag.json" +tiers=(vlt adc nf sp) +function frame_file () { + local tier="$1"; shift + echo "frames-$tier.tar.gz" +} + +# Compile Jsonnet to dag.json and run wire-cell. +setup_file () { + + skip_if_no_input + + cd_tmp file + + local cfg_file="${BATS_TEST_FILENAME%.bats}.jsonnet" + [[ -n "$cfg_file" ]] + [[ -s "$cfg_file" ]] + + local in_file="$(input_file depos/many.tar.bz2)" + echo "in file: $in_file" + [[ -f "$in_file" ]] + + declare -a args1=( $cfg_file $dag_file -A input="$in_file" ) + declare -a deps1=( -s "$cfg_file" -s "$in_file" -t "$dag_file" ) + declare -a deps2=( -s "$dag_file" -s "$in_file" ) + for tier in ${tiers[@]} + do + local ff=$(frame_file $tier) + # echo "TIER: \"$tier\" ff=\"$ff\"" 1>&3 + args1+=( -A "${tier}output=${ff}" ) + deps2+=( -t $ff ) + done + run_idempotently --verbose ${deps1[@]} -- compile_jsonnet ${args1[@]} + [[ -s "$dag_file" ]] + + run_idempotently ${deps2[@]} -- wct -l $log_file -L debug -c $dag_file + [[ -s "$log_file" ]] + + for tier in ${tiers[@]} + do + local ff=$(frame_file $tier) + file_larger_than "$ff" 32 + done + + [[ -z "$(grep ' E ' $log_file)" ]] +} + +# bats test_tags=history +@test "make history" { + cd_tmp file + + [[ -s "$dag_file" ]] + saveout -c history "$dag_file" + for tier in ${tiers[@]} + do + local ff=$(frame_file $tier) + [[ -s "$ff" ]] + saveout -c history "$ff" + done +} + + +function plotframe () { + skip_if_no_input + + cd_tmp file + + local name="$1"; shift + local tier="$1" ; shift + local type="$1"; shift + local dat="$1"; shift + local fig="${name}-${tier}-${type}.png" + local tag='*' + declare -a args + if [ "$tier" = "vlt" ] ; then + args=(--unit 'millivolt' --vmin -20 --vmax 20) + fi + if [ "$tier" = "adc" ] ; then + args=(--unit 'ADC' --vmin -25 --vmax 25) + tag='orig0' + fi + if [ "$tier" = "nf" ] ; then + args=(--unit 'ADC' --vmin -25 --vmax 25) + tag='raw0' + fi + if [ "$tier" = "sp" ] ; then + args=(--unit 'eplus' --vmin 0 --vmax 5000) + tag='gauss0' + fi + + local wcplot=$(wcb_env_value WCPLOT) + [[ -n "$wcplot" ]] + [[ -x "$wcplot" ]] + + run_idempotently -s $dat -t $fig -- \ + $wcplot frame ${args[@]} --tag "$tag" --single --name $type --output "$fig" "$dat" + [[ -s "$fig" ]] + saveout -c plots $fig +} + +@test "plot frame vlt wave new" { + plotframe current vlt wave "$(frame_file vlt)" +} +@test "plot frame adc wave new" { + plotframe current adc wave "$(frame_file adc)" +} +@test "plot frame nf wave new" { + plotframe current nf wave "$(frame_file nf)" +} +@test "plot frame sp wave new" { + plotframe current sp wave "$(frame_file sp)" +} +@test "plot frame adc wave old" { + plotframe blessed adc wave "$(input_file frames/pdsp-signal-noise.tar.gz)" +} + +function comp1d () { + skip_if_no_input + cd_tmp file + + plot=$1; shift + [[ -n "$plot" ]] + tier="adc" + + local wcplot=$(wcb_env_value WCPLOT) + local new_dat="$(realpath $(frame_file $tier))" + [[ -s "$new_dat" ]] + local old_dat="$(input_file frames/pdsp-signal-noise.tar.gz)" + [[ -s "$old_dat" ]] + + local fig="${plot}.png" + + run_idempotently -s $new_dat -s $old_dat -t $fig -- \ + $wcplot comp1d -s --chmin 0 --chmax 800 \ + -n $plot --transform ac \ + -o $fig $old_dat $new_dat + [[ -s "$fig" ]] + + saveout -c plots $fig +} + +@test "plot comp1d wave" { + comp1d wave +} +@test "plot comp1d spec" { + comp1d spec +} + + +@test "dotify dag viz" { + cd_tmp file + dotify_graph $dag_file dag.svg +} diff --git a/test/test/test-pdsp-simsn-nfsp.jsonnet b/test/test/test-pdsp-simsn-nfsp.jsonnet new file mode 100644 index 000000000..d12da7066 --- /dev/null +++ b/test/test/test-pdsp-simsn-nfsp.jsonnet @@ -0,0 +1,182 @@ +// This is used by test-pdsp-simsn.bats. + +local pg = import 'pgraph.jsonnet'; +local wc = import 'wirecell.jsonnet'; +local io = import 'pgrapher/common/fileio.jsonnet'; + +local tools_maker = import 'pgrapher/common/tools.jsonnet'; +local base = import 'pgrapher/experiment/pdsp/simparams.jsonnet'; +local params = base { + lar: super.lar { + // Longitudinal diffusion constant + // DL: std.extVar('DL') * wc.cm2 / wc.s, + DL: 4.0 * wc.cm2 / wc.s, + // Transverse diffusion constant + // DT: std.extVar('DT') * wc.cm2 / wc.s, + DT: 8.8 * wc.cm2 / wc.s, + // Electron lifetime + // lifetime: std.extVar('lifetime') * wc.ms, + lifetime: 10.4 * wc.ms, + // Electron drift speed, assumes a certain applied E-field + // drift_speed: std.extVar('driftSpeed') * wc.mm / wc.us, + drift_speed: 1.565 * wc.mm / wc.us, + }, +}; + +local tools = tools_maker(params); + +// We only use first anode. +local anode = tools.anodes[0]; +local pirs = tools.pirs[0]; + +// +// Simulation +// +local sim_maker = import "pgrapher/common/sim/nodes.jsonnet"; +local sim = sim_maker(params, tools); + +local drifter = sim.drifter; +local setdrifter = pg.pnode({ + type: 'DepoSetDrifter', + data: { + drifter: "Drifter" + } +}, nin=1, nout=1, uses=[drifter]); + +local transform = pg.pnode({ + type:'DepoTransform', + name: "", + data: { + rng: wc.tn(tools.random), + dft: wc.tn(tools.dft), + anode: wc.tn(anode), + pirs: std.map(function(pir) wc.tn(pir), pirs), + fluctuate: false, // minimize randomness + drift_speed: params.lar.drift_speed, + first_frame_number: 100, // just to be different.... + readout_time: params.sim.ductor.readout_time, + start_time: params.sim.ductor.start_time, + tick: params.daq.tick, + nsigma: 3, + }, +}, nin=1, nout=1, uses=[anode, tools.random, tools.dft] + pirs); +local reframer = pg.pnode({ + type: 'Reframer', + name: '', + data: { + anode: wc.tn(anode), + tbin: params.sim.reframer.tbin, // 120 + nticks: params.sim.reframer.nticks, + }, +}, nin=1, nout=1); +local noise_model = { + type: "EmpiricalNoiseModel", + name: "", + data: { + anode: wc.tn(anode), + dft: wc.tn(tools.dft), + chanstat: "", + spectra_file: params.files.noise, + nsamples: params.daq.nticks, + period: params.daq.tick, + wire_length_scale: 1.0*wc.cm, // optimization binning + }, + uses: [anode, tools.dft], +}; +local addnoise = pg.pnode({ + type: "AddNoise", + name: "", + data: { + rng: wc.tn(tools.random), + dft: wc.tn(tools.dft), + model: wc.tn(noise_model), + nsamples: params.daq.nticks, + replacement_percentage: 0.02, // random optimization + }}, nin=1, nout=1, uses=[tools.random, tools.dft, noise_model]); + +local digitizer = pg.pnode({ + type: "Digitizer", + name: "", + data : params.adc { + anode: wc.tn(anode), + frame_tag: "orig0", + } +}, nin=1, nout=1, uses=[anode]); + +// Break up pipeline between signal voltage and adding noise so we can +// save out individual. +local vlt = pg.pipeline([setdrifter, transform, reframer]); +local noi = pg.pipeline([addnoise, digitizer]); + + +// +// Noise Filter +// +local perfect = import 'pgrapher/experiment/pdsp/chndb-perfect.jsonnet'; +local chndb = { + type: 'OmniChannelNoiseDB', + name: '', + data: perfect(params, anode, tools.field, 0){ + dft:wc.tn(tools.dft) + }, + uses: [anode, tools.field, tools.dft]}; + +local nf_maker = import 'pgrapher/experiment/pdsp/nf.jsonnet'; +local nf = nf_maker(params, anode, chndb, 0, ""); + +// +// Signal Processing +// +local sp_maker = import 'pgrapher/experiment/pdsp/sp.jsonnet'; +local sp = sp_maker(params, tools).make_sigproc(anode); + +// I/O + +local depo_source(input) = pg.pnode({ + type: 'DepoFileSource', + name: wc.basename(input), + data: { inname: input } // "depos.tar.bz2" +}, nin=0, nout=1); + +local sio_sink(output, tags=[]) = pg.pnode({ + type: "FrameFileSink", + name: wc.basename(output), + data: { + outname: output, // "frames.tar.bz2", + tags: tags, + digitize: false, + }, +}, nin=1, nout=0); + +local sio_tap(output, tags=[]) = pg.fan.tap('FrameFanout', sio_sink(output, tags), wc.basename(output)); + +local plugins = [ "WireCellSio", "WireCellGen", "WireCellSigProc","WireCellApps", "WireCellPgraph", "WireCellTbb", "WireCellRoot"]; + +function(input="input.npz", + vltoutput="frames-vlt.npz", adcoutput="frames-adc.npz", + nfoutput="frames-nf.npz", spoutput="frames-sp.npz") + + local graph = pg.pipeline([depo_source(input), + vlt, sio_tap(vltoutput), + noi, sio_tap(adcoutput, ["orig0"]), + nf, sio_tap(nfoutput, ["raw0"]), + sp, sio_sink(spoutput, ["gauss0","wiener0"]) + ]); + + // Pgrapher or TbbFlow + local engine = "Pgrapher"; + local app = { + type: 'Pgrapher', + data: { + edges: pg.edges(graph), + }, + }; + local cmdline = { + type: "wire-cell", + data: { + plugins: plugins, + apps: ["Pgrapher"], + } + }; + // Finally, the configuration sequence which is emitted. + [cmdline] + pg.uses(graph) + [app] diff --git a/test/test/test-pdsp-simsn.bats b/test/test/test-pdsp-simsn.bats deleted file mode 100644 index fc9746084..000000000 --- a/test/test/test-pdsp-simsn.bats +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env bats - -# fixme: add asserts -# fixme: use history mechanism - -# bats file_tags=implicit,plots - -# Some tests for https://github.com/WireCell/wire-cell-toolkit/pull/195 - -bats_load_library wct-bats.sh - -# FIXME: this test provides use cases for the test system -# -# - [x] A systematic way to run a bats test in a pre-created directory -# under build/tests// to keep build tidy. --> cd_tmp -# -# -# - [x] A "test data repository" with files for input and expected -# output. --> ./wcb --test-data=/... and wct-bats.sh helpers -# -# - This test implements the commands from the README.md in above URL. It produces PDFs which clearly show a problem, but require a human. In addition to the PDFs, this test should compare data at the "frames" file level. This likely needs a "wirecell-util diff-array" command to be written. -# -# - This test is laboriously written to be idempotent. Perhaps the common patterns can be abstracted. -# -# - A common patter to name and extract "human interesting" products of the test, such as the PDFs. - -base="$(basename ${BATS_TEST_FILENAME} .bats)" - -setup_file () { - - skip_if_no_input - - cd_tmp file - - # Assume we run from build/. - cfgfile="$(relative_path pdsp-depos-simsn-frames.jsonnet)" - echo "using config: $cfgfile" - [[ -n "$cfgfile" ]] - [[ -f "$cfgfile" ]] - - # Assure input file - # was pdsp/sim/sn/depos.tar.bz - infile="$(input_file depos/many.tar.bz2)" - echo "infile: $infile" - [[ -f "$infile" ]] - - cd_tmp - - logfile="${base}.log" - outfile="${base}_frames.tar.gz" - if [ -f "$logfile" -a -f "$outfile" ] ; then - warn "Reusing existing output of wire-cell on $cfgfile" - else - echo "Running wire-cell on $cfgfile" - wct -l "$logfile" -L debug \ - -A input="$infile" \ - -A output="$outfile" \ - -c "$cfgfile" - fi - [[ -f "$logfile" ]] - [[ -s "$logfile" ]] - [[ -f "$outfile" ]] - [[ -s "$outfile" ]] - saveout -c history "$outfile" -} - - - -function waveform_figure () { - local dat="$1" ; shift - [[ -n "$dat" ]] - [[ -f "$dat" ]] - local fig="$1"; shift - [[ -n "$fig" ]] - - if [ -f "$fig" ] ; then - warn "Reusing existing $fig" - return - fi - - local wcplot=$(wcb_env_value WCPLOT) - - # yell "$wcplot frame -s -n wave $dat $fig" - run $wcplot frame -s -n wave "$dat" "$fig" - pwd - echo "$output" - echo "status: $status" - echo "status okay" - [[ "$status" -eq 0 ]] - echo "no fig: $fig" - ls -l *.png - [[ -s "$fig" ]] - - saveout -c plots "$fig" -} - - -function channel_figure () { - local chan="$1"; shift - local old_dat="$1"; shift - local new_dat="$1"; shift - local fig="$1"; shift - - if [ -f "$fig" ] ; then - warn "Reusing existing $fig" - fi - local wcplot=$(wcb_env_value WCPLOT) - # yell "$wcplot comp1d -c $chan $old_dat $new_dat $fig" - - run $wcplot comp1d -s -c $chan $old_dat $new_dat $fig - echo "$output" - [[ "$status" -eq 0 ]] - [[ -s "$fig" ]] - saveout -c plots "$fig" -} - - -@test "plot pdsp signal" { - - skip_if_no_input - - cd_tmp file - - outfile="${base}_frames.tar.gz" - - # make figures - local fmt="png" - - # make waveform figures for new data and old ("blessed") data. - local new_dat="$outfile" - local new_fig="${base}_frame_new.$fmt" - waveform_figure $new_dat $new_fig - - # was pdsp/sim/sn/frames-expected.tar.bz2 - local old_dat="$(input_file frames/pdsp-signal-noise.tar.bz2)" - [[ -n "$old_dat" ]] - local old_fig="${base}_frame_old.$fmt" - waveform_figure $old_dat $old_fig - - local wcplot=$(wcb_env_value WCPLOT) - for plot in wave spec - do - local fig="check-pdsp-signal-${plot}.png" - $wcplot comp1d -s --chmin 0 --chmax 800 \ - -n $plot --transform ac \ - -o $fig -s $old_dat $new_dat - [[ -s $fig ]] - saveout -c plots $fig - done -} - diff --git a/test/test/test_wct_bats.bats b/test/test/test_wct_bats.bats index 028144dcd..840ff01fb 100644 --- a/test/test/test_wct_bats.bats +++ b/test/test/test_wct_bats.bats @@ -186,3 +186,35 @@ bats_load_library wct-bats.sh } + +@test "run idempotently" { + cd_tmp + + for f in file1.txt file2.txt + do + rm -f $f + # unconditinally run + run_idempotently -- touch $f + [[ -f $f ]] + done + + rm -f file3.txt + run_idempotently -s file1.txt -t file3.txt -- cp file1.txt file3.txt + [[ -f file3.txt ]] + + sync # can run fast enough that file3.txt isn't fully created? + touch timestamp.txt # make this just to mark the time for later. + + # this should not update file3.txt + run_idempotently -s file1.txt -t file3.txt -- cp file1.txt file3.txt + [[ -f file3.txt ]] + [[ file3.txt -ot timestamp.txt ]] + [[ timestamp.txt -nt file3.txt ]] + + # this should update file3 + run_idempotently -s timestamp.txt -t file3.txt -- cp file1.txt file3.txt + [[ -f file3.txt ]] + [[ file1.txt -ot file3.txt ]] + [[ file3.txt -nt file1.txt ]] + +} diff --git a/test/wct-bats.sh b/test/wct-bats.sh index 9150f0528..10e942efe 100644 --- a/test/wct-bats.sh +++ b/test/wct-bats.sh @@ -31,6 +31,77 @@ function die () { } +# Assure a file is larger than given number of bytes +# +# usage: +# file_larger_than +function file_larger_than () { + local filename=$1 ; shift + local minsize=$1; shift + [[ -f "$filename" ]] + [[ -n "$minsize" ]] + [[ "$(stat -c '%s' $filename)" -gt "$minsize" ]] +} + + +# Run command if any target file is missing or older than any source file. +# +# usage: +# run_idempotently [-v/--verbose] [-s/--source ] [-t/--target ] -- +# +# May give multiple -s or -t args +function run_idempotently () { + + declare -a src + declare -a tgt + local verbose="no" + while [[ $# -gt 0 ]] ; do + case $1 in + -s|--source) src+=( $2 ) ; shift 2;; + -t|--target) tgt+=( $2 ) ; shift 2;; + -v|--verbose) verbose="yes"; shift;; + --) shift; break; + esac + done + + local need_to_run="no" + if [ -z "$src" -o -z "$tgt" ] ; then + # Always run if sink, source or atomic. + need_to_run="yes" + fi + if [ "$need_to_run" = "no" ] ; then + # Run if missing any targets + for one in "${tgt[@]}" + do + if [ ! -f "$one" ] ; then + need_to_run="yes" + fi + done + fi + if [ "$need_to_run" = "no" ] ; then + # Run if any source is newer than any targeta + local src_newest="$(ls -t ${src[@]} | head -1)" + local tgt_oldest="$(ls -t ${tgt[@]} | tail -1)" + + if [ "$src_newest" -nt "$tgt_oldest" ] ; then + need_to_run="yes" + fi + fi + + if [ "$need_to_run" = "no" ] ; then + yell "target newer than source not running: $@" + return + fi + + echo "running: $@" + if [ "$verbose" = "yes" ] ; then + yell "running: $@" + fi + run "$@" + echo "$output" + [[ "$status" -eq 0 ]] +} + # Return shell environment function dumpenv () { env | grep = | sort @@ -55,7 +126,15 @@ function tojson () { # Return the top level source directory based on this file. function topdir () { - dirname $(dirname $(realpath $BASH_SOURCE)) + local name=$BASH_SOURCE + [[ -n "$name" ]] + name="$(realpath $name)" + [[ -n "$name" ]] + name="$(dirname $name)" + [[ -n "$name" ]] + name="$(dirname $name)" + [[ -n "$name" ]] + echo "$name" } # Return the build directory. @@ -92,6 +171,7 @@ function srcdir () { # - is the current wire-cell version # - is from BATS_TEST_FILENAME without the extension. function saveout () { + declare -a src=() subdir="output" tgt="" @@ -177,8 +257,8 @@ function wcb () { function wcb_env_dump () { # Keep a cache if running in bats. - if [ -n "${BATS_RUN_TMPDIR}" ] ; then - local cache="${BATS_RUN_TMPDIR:-/tmp}/wcb_env.txt" + if [ -n "${BATS_FILE_TMPDIR}" ] ; then + local cache="${BATS_FILE_TMPDIR}/wcb_env.txt" if [ ! -f $cache ] ; then wcb dumpenv | grep 'wcb: ' | sed -e 's/^wcb: '// | sort > $cache fi @@ -257,34 +337,43 @@ function usepkg () { # usage: # compile_jsonnet file.jsonnet file.json [-A/-V etc options, no -J/-P] function compile_jsonnet () { - local ifile=$1 ; shift - local ofile=$1 ; shift + local ifile="$1" ; shift + local ofile="$1" ; shift local cfgdir="$(topdir)/cfg" local orig_wcpath=$WIRECELL_PATH WIRECELL_PATH="" - cmd=$(wcb_env_value WCSONNET) - if [ -n "$cmd" -a -x "$cmd" ] ; then - ## switch to this when wcsonnet gets "-o" - # cmd="$cmd -P $cfgdir -o $ofile $@ $ifile" - ## until then - cmd="$cmd -o $ofile -P $cfgdir $@ $ifile" + local wcsonnet=$(wcb_env_value WCSONNET) + [[ -n "$wcsonnet" ]] + if [ -z "$wcsonnet" ] ; then + yell "Failed to get WCSONNET! Cache problem?" + exit 1 fi - if [ -z "$cmd" ] ; then - cmd=$(wcb_env_value JSONNET) - if [ -z "cmd" ] ; then - cmd="jsonnet" # hail mary - fi - if [ -n "$cmd" -a -x "$cmd" ] ; then - cmd="$cmd -o $ofile -J $cfgdir $@ $ifile" - fi - fi - [[ -n "$cmd" ]] - echo "$cmd" - run $cmd + declare -a cmd=( "$wcsonnet" -o "$ofile" -P "$cfgdir" "$@" "$ifile" ) + + # cmd=( $(wcb_env_value WCSONNET) ) + # if [ -n "$cmd" -a -x "$cmd" ] ; then + # ## switch to this when wcsonnet gets "-o" + # # cmd="$cmd -P $cfgdir -o $ofile $@ $ifile" + # ## until then + # cmd+=(-o "$ofile" -P "$cfgdir" "$@" "$ifile") + # fi + # if [ -z "$cmd" ] ; then + # cmd=$(wcb_env_value JSONNET) + # if [ -z "cmd" ] ; then + # cmd="jsonnet" # hail mary + # fi + # if [ -n "$cmd" -a -x "$cmd" ] ; then + # cmd="$cmd -o $ofile -J $cfgdir $@ $ifile" + # fi + # fi + # [[ -n "$cmd" ]] + + run "${cmd[@]}" echo "$output" [[ "$status" -eq 0 ]] + [[ -s "$ofile" ]] WIRECELL_PATH="$orig_wcpath" } @@ -298,6 +387,7 @@ function wct () { [[ -n "$cli" ]] [[ -x "$cli" ]] + echo "$cli $@" # for output on failure run $cli $@ echo "$output" [[ "$status" -eq 0 ]] @@ -305,7 +395,10 @@ function wct () { # Emit version string function version() { - wct --version + cli=$(wcb_env_value WIRE_CELL) + [[ -n "$cli" ]] + [[ -x "$cli" ]] + $cli --version } # Execute the "wirecell-pgraph dotify" program under Bats "run". @@ -402,6 +495,7 @@ function cd_tmp () { # local mycfg=$(relative_path my-cfg-jsonnet) function relative_path () { local want="$1"; shift + [[ -n "$BATS_TEST_FILENAME" ]] realpath "$(dirname $BATS_TEST_FILENAME)/$want" } @@ -512,6 +606,7 @@ function config_path () { function resolve_file () { local want="$1" ; shift + [[ -n "$BATS_TEST_FILENAME" ]] local mydir="$(dirname ${BATS_TEST_FILENAME})" local t="$(topdir)" local paths=$(resolve_pathlist "$t/test/data" "$t" "$t/build/tests" ${WIRECELL_PATH}) @@ -642,6 +737,8 @@ function historical_versions () { # One or more -v/--version options can add versions to the list of historical versions. # # A -l/--last number will return only the more recent versions (lexical sort) +# +# A -c/--current will include the current software version function historical_files () { local versions=( $(historical_versions) ) local last="" @@ -650,6 +747,7 @@ function historical_files () { case $1 in -l|--last) last="$2"; shift 2;; -v|--version) versions+=( $2 ); shift 2;; + -c|--current) versions+=( $(version) ); shift;; -*) die "unknown option $1" ;; *) paths+=( $1 ); shift;; esac @@ -678,7 +776,7 @@ function skip_if_no_category () { local cat="history" while [[ $# -gt 0 ]] ; do case $1 in - -v|--version) version="$2"; shift 2;; + -v|--version) ver="$2"; shift 2;; *) category="$1"; shift;; esac done diff --git a/waft/datarepo.py b/waft/datarepo.py index 39f82145d..dd364ae90 100644 --- a/waft/datarepo.py +++ b/waft/datarepo.py @@ -11,7 +11,6 @@ import io from waflib.Logs import debug, info, error, warn -import waflib.Utils from waflib.Task import Task try: from urllib import request @@ -183,6 +182,9 @@ def build(bld): ''' Download and unpack archived data repo files. ''' + if not bld.env.TESTS: + return + bld(features="datarepo", name='datarepo_input') for ver in bld.env.TEST_DATA_VERSIONS: name = 'datarepo_history_' + ver.replace('.','_') @@ -203,40 +205,3 @@ def build(bld): # source = tdir.find_or_declare(f'history-{ver}.url')) - -def packrepo(bld): - ''' - Pack existing test data repo to archive files. - ''' - indir=bld.path.find_dir("build/tests/input") - if indir.exists(): - cmd = "tar -C %s -cf input.tar input" % (indir.parent.abspath()) - info(cmd) - rc = waflib.Utils.subprocess.call(cmd, shell=True) - if rc != 0: - warn("archive failed") - else: - warn("no input files found") - - - - rels = bld.options.test_data_releases - if rels: - rels = [r.strip() for r in rels.split(",") if r.strip()] - - hdir = bld.path.find_dir("build/tests/history") - # print(hdir) - hdirs = hdir.ant_glob("*") # this finds children but doesn't return them? - # print(hdirs) - # hdir.mkdir() - # print(hdir.children) - for vdir in hdir.children: - if rels and vdir not in rels: - continue - cmd = "tar -C %s -cf history-%s.tar history/%s" % ( - hdir.parent.abspath(), vdir, vdir) - info(cmd) - rc = waflib.Utils.subprocess.call(cmd, shell=True) - if rc != 0: - warn("archive failed") - diff --git a/waft/smplpkgs.py b/waft/smplpkgs.py index 1b2f7fccd..3c08738b5 100644 --- a/waft/smplpkgs.py +++ b/waft/smplpkgs.py @@ -69,16 +69,16 @@ def add_edge(self, edge, **kwds): def options(opt): opt.load('compiler_cxx') - opt.load("datarepo") opt.load('wcb_unit_test') # adds --tests + opt.load("datarepo") opt.add_option("--docs", default="", help="comma separated list of docs to generate. eg: 'org2hml,org2pdf,doxy'. default:none") def configure(cfg): cfg.load('compiler_cxx') - cfg.load('datarepo') cfg.load('wcb_unit_test') + cfg.load('datarepo') cfg.load('rpathify') cfg.load('org') diff --git a/waft/wcb.py b/waft/wcb.py index f441216a4..c1ae7c859 100644 --- a/waft/wcb.py +++ b/waft/wcb.py @@ -3,7 +3,7 @@ import generic import os.path as osp -from waflib.Utils import to_list +from waflib.Utils import to_list, subprocess from waflib.Logs import debug, info, error, warn from waflib.Build import BuildContext from waflib.Configure import conf @@ -247,3 +247,38 @@ def dumpenv(bld): class DumpenvContext(BuildContext): cmd = 'dumpenv' fun = 'dumpenv' + +def packrepo(bld): + ''' + Pack existing test data repo to archive files. + ''' + + indir=bld.path.find_dir("build/tests/input") + if indir.exists(): + cmd = "tar -C %s -cf input.tar input" % (indir.parent.abspath()) + info(cmd) + rc = subprocess.call(cmd, shell=True) + if rc != 0: + warn("archive failed") + else: + warn("no input files found") + + rels = bld.env.TEST_DATA_VERSIONS + + hdir = bld.path.find_dir("build/tests/history") + hdirs = hdir.ant_glob("*") + + for vdir in hdir.children: + if rels and vdir not in rels: + info(f"skip {vdir}, not in releases") + continue + cmd = "tar -C %s -cf history-%s.tar history/%s" % ( + hdir.parent.abspath(), vdir, vdir) + info(cmd) + rc = subprocess.call(cmd, shell=True) + if rc != 0: + warn("archive failed") + +class PackrepoenvContext(BuildContext): + cmd = 'packrepo' + fun = 'packrepo' diff --git a/wscript b/wscript index 8b124f867..ed7c24286 100644 --- a/wscript +++ b/wscript @@ -85,5 +85,4 @@ def dumpenv(bld): bld.load('wcb') def packrepo(bld): - import datarepo - datarepo.pakrepo(bld) + bld.load('wcb')