You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by sl...@apache.org on 2022/11/08 16:54:50 UTC

[daffodil] branch main updated: Refactor integration tests for clarity and speed

This is an automated email from the ASF dual-hosted git repository.

slawrence pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git


The following commit(s) were added to refs/heads/main by this push:
     new 1a0954a7b Refactor integration tests for clarity and speed
1a0954a7b is described below

commit 1a0954a7b67bbe6c18b9396a8ac38f952b57355f
Author: Steve Lawrence <sl...@apache.org>
AuthorDate: Fri Nov 4 12:17:08 2022 -0400

    Refactor integration tests for clarity and speed
    
    The current integration tests have too much boilerplate that can be
    refactored with some new APIs. Additionally, the integration tests are
    incredibly slow due to all the forking and creation of new VMs. This
    fixes both of these issues.
    
    The new integration test API creates a new `runCLI` function, accepting
    an array of string arguments (with a new "args" string interpolator to
    make specifying this array easier), various execution parameters, a
    function that contains the normal `Expect` logic all our of integration
    tests already use, and finally the expected exit code.
    
    A new `path` function is created to help deal with paths in a OS
    agnostic manner.
    
    New `withTempFile` and `withTempDirectory` functions are added to help
    with creating temporary files in a special temporary daffodil directory,
    and to ensure they are cleaned up when the test is complete.
    
    A new `withSysProp` function is used to temporarily change and restore
    system properties, helping to avoid changes that might interfere with
    other tests.
    
    With these new functions, integration tests now look something like
    this:
    
        val schema = path("path/to/schema.dfdl.xsd")
        runCLI(args"parse -s $schema") { cli =>
          cli.send("data to parse", inputDone = true)
          cli.expect("<data>string to expect</data>")
        } (ExitCode.Success)
    
    The runCLI function supports executing Daffodil in either a new process
    or thread, defaulting to thread unless a test requires a separator
    process (e.g. classpath changes). Streams are setup for handling
    stdin/out/err. When threaded, the thread calls the
    org.apache.daffodil.Main run function, and configures Main and log4j to
    output to these streams. When using a new processing, the daffodil
    sh/bat script is directly executed.
    
    A new `Expect` instance is configured so that it can read/write to these
    streams and validate the output, with a new CLITester class used to
    provide helper wrappers around `Expect` to make logic a bit less
    verbose.
    
    Multiple changes were also made to Main and the Debugger to ensure they
    always use the specified streams instead of always using stdin/out/err.
    This is really only needed for this integration testing, but could
    potentially be useful in other use cases where stdin/out/err need need
    to be mimicked without forking a new process.
    
    Note that the thread approach isn't thread safe, but integration tests
    are already not run in parallel. By using threads instead of spawning
    new processes where possible, we drastically decrease the overhead, with
    test suites now taking a number of seconds instead of a number of
    minutes, averaging and order of magnitude speed up.
    
    This also tweaks various CLI tests to not expect data from files,
    instead hardcoding what to expect in each test. This is a bit less
    exact, but it make the logic simpler and more consistent, and allows us
    to remove many files from the repo. Also found and removed many debugger
    command files that are never actually used.
    
    This also enables the blob CLI tests but adds an assumption so that they
    are skipped if the large input files have not been generated. This gives
    us a reminder that those tests exist and we can periodically ensure they
    work by generating the files.
    
    This also ensures coverage is enabled in the github workflow every time
    we execute SBT. Otherwise, changes of coverage enablement cause a
    recompile. By doing this we avoid recompiling the same files multiple
    times which saves a couple of minutes overall. Note that we originally
    disabled coverage on some steps because it caused failures, but whatever
    caused that seems to have been fixed.
    
    DAFFODIL-2381
---
 .github/workflows/main.yml                         |    8 +-
 .../org/apache/daffodil/CLI/debugger/1326          |   12 -
 .../org/apache/daffodil/CLI/debugger/1328          |   11 -
 .../org/apache/daffodil/CLI/debugger/1329          |    9 -
 .../org/apache/daffodil/CLI/debugger/1330          |   10 -
 .../org/apache/daffodil/CLI/debugger/1331          |   18 -
 .../org/apache/daffodil/CLI/debugger/1333          |   13 -
 .../org/apache/daffodil/CLI/debugger/1334          |    8 -
 .../org/apache/daffodil/CLI/debugger/1337          |    9 -
 .../org/apache/daffodil/CLI/debugger/1338          |    7 -
 .../org/apache/daffodil/CLI/debugger/1339          |    5 -
 .../org/apache/daffodil/CLI/debugger/1340          |    6 -
 .../org/apache/daffodil/CLI/debugger/1382          |   12 -
 .../org/apache/daffodil/CLI/debugger/1591          |    2 -
 .../org/apache/daffodil/CLI/debugger/1602          |    5 -
 .../org/apache/daffodil/CLI/debugger/1863          |    8 -
 .../resources/org/apache/daffodil/CLI/debugger/982 |    2 -
 .../org/apache/daffodil/CLI/output/output1.txt     |    7 -
 .../org/apache/daffodil/CLI/output/output10.txt    |    5 -
 .../org/apache/daffodil/CLI/output/output11.txt    |    5 -
 .../org/apache/daffodil/CLI/output/output12.txt    |    5 -
 .../org/apache/daffodil/CLI/output/output13.txt    |   11 -
 .../org/apache/daffodil/CLI/output/output14.txt    |   27 -
 .../org/apache/daffodil/CLI/output/output15.txt    |   90 -
 .../org/apache/daffodil/CLI/output/output16.txt    |   39 -
 .../daffodil/CLI/output/output1_nopretty.txt       |    1 -
 .../org/apache/daffodil/CLI/output/output2.txt     |    8 -
 .../org/apache/daffodil/CLI/output/output3.txt     |    1 -
 .../org/apache/daffodil/CLI/output/output4.txt     |    6 -
 .../org/apache/daffodil/CLI/output/output5.txt     |    6 -
 .../org/apache/daffodil/CLI/output/output6.txt     |   22 -
 .../org/apache/daffodil/CLI/output/output8.txt     |   29 -
 .../org/apache/daffodil/CLI/output/output9.txt     |    5 -
 .../apache/daffodil/CLI/output/output_DFDL-714.txt |    3 -
 .../CLI/output/unqualified_path_step_01.txt        |    7 -
 .../CLI/output/unqualified_path_step_02.txt        |    7 -
 .../CLI/output/unqualified_path_step_03.txt        |    7 -
 .../CLI/output/unqualified_path_step_04.txt        |    7 -
 .../org/apache/daffodil/CLI/xcatalog_invalid.xml   |    2 +-
 .../it/scala/org/apache/daffodil/CLI/Util.scala    |  586 ++++--
 .../scala/org/apache/daffodil/blob/TestBlob.scala  |  227 +--
 .../apache/daffodil/debugger/TestCLIDebugger.scala | 1977 ++++++++------------
 .../daffodil/executing/TestCLIexecuting.scala      |  245 ---
 .../daffodil/generating/TestCLIGenerateC.scala     |  221 +--
 .../apache/daffodil/listing/TestCLIListing.scala   |   96 -
 .../apache/daffodil/parsing/TestCLIParsing.scala   | 1533 ++++-----------
 .../daffodil/performance/TestCLIPerformance.scala  |  386 ++--
 .../apache/daffodil/saving/TestCLISaveParser.scala |  505 ++---
 .../apache/daffodil/schematron/TestEmbedded.scala  |  152 +-
 .../daffodil/schematron/TestSvrlOutput.scala       |  218 ++-
 .../daffodil/schematron/TestValidating.scala       |   41 +-
 .../org/apache/daffodil/schematron/package.scala   |  147 --
 .../org/apache/daffodil/tdml/TestCLItdml.scala     |  128 ++
 .../apache/daffodil/tunables/TestCLITunables.scala |  334 ++--
 .../daffodil/tunables/TestCLITunables2.scala       |  145 --
 .../org/apache/daffodil/udf/TestCLIUdfs.scala      |  754 ++------
 .../daffodil/unparsing/TestCLIUnparsing.scala      |  752 ++------
 .../src/main/scala/org/apache/daffodil/Main.scala  |  196 +-
 .../daffodil/debugger/CLIDebuggerRunner.scala      |   38 +-
 .../daffodil/dpath/DFDLExpressionParser.scala      |    5 +-
 .../daffodil/debugger/InteractiveDebugger.scala    |    4 +-
 .../daffodil/debugger/TraceDebuggerRunner.scala    |    6 +-
 .../org/apache/daffodil/infoset/InfosetImpl.scala  |    1 -
 project/Rat.scala                                  |   37 -
 64 files changed, 3155 insertions(+), 6024 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 608f8e8a3..2e219d2ef 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -54,7 +54,7 @@ jobs:
     env:
       AR: ${{ matrix.env_ar }}
       CC: ${{ matrix.env_cc }}
-      SBT: sbt -J-Xms1024m -J-Xmx5120m -J-XX:ReservedCodeCacheSize=512m -J-XX:MaxMetaspaceSize=1024m ++${{ matrix.scala_version }}
+      SBT: sbt -J-Xms1024m -J-Xmx5120m -J-XX:ReservedCodeCacheSize=512m -J-XX:MaxMetaspaceSize=1024m ++${{ matrix.scala_version }} coverage
       SONARSCAN: ${{
                      matrix.os == 'ubuntu-20.04' &&
                      matrix.java_version == '11' &&
@@ -125,7 +125,7 @@ jobs:
       ############################################################
 
       - name: Compile
-        run: $SBT coverage compile Test/compile IntegrationTest/compile
+        run: $SBT compile Test/compile IntegrationTest/compile
 
       - name: Build Documentation
         run: $SBT unidoc
@@ -145,10 +145,10 @@ jobs:
         run: $SBT ratCheck || (cat target/rat.txt; exit 1)
 
       - name: Run Unit Tests
-        run: $SBT coverage test
+        run: $SBT test
 
       - name: Run Integration Tests
-        run: $SBT coverage IntegrationTest/test
+        run: $SBT IntegrationTest/test
 
       - name: Run Modified Example Files Check
         run: git diff --color --exit-code
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1326 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1326
deleted file mode 100644
index 2f98dd5f7..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1326
+++ /dev/null
@@ -1,12 +0,0 @@
-display eval (.)
-step
-info displays
-disable display 1
-step
-enable display 1
-step
-delete display 1
-step
-enable display 1
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1328 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1328
deleted file mode 100644
index a04a694b8..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1328
+++ /dev/null
@@ -1,11 +0,0 @@
-display info infoset
-break element.cell
-continue
-step
-step
-continue
-step
-step
-delete breakpoint 1
-continue
-exit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1329 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1329
deleted file mode 100644
index 2c6982376..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1329
+++ /dev/null
@@ -1,9 +0,0 @@
-display info infoset
-break element.cell
-condition 1 dfdl:occursIndex() = 3
-info breakpoints
-continue
-step
-step
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1330 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1330
deleted file mode 100644
index 64b275d7f..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1330
+++ /dev/null
@@ -1,10 +0,0 @@
-display info occursIndex
-break element.cell
-info breakpoints
-continue
-continue 
-disable breakpoint 1
-info breakpoints
-info data
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1331 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1331
deleted file mode 100644
index 5b7c50ca0..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1331
+++ /dev/null
@@ -1,18 +0,0 @@
-break element.cell
-break element.cell
-condition 1 dfdl:occursIndex() mod 2 = 1
-condition 2 dfdl:occursIndex() mod 2 = 0
-info breakpoints
-display info occursIndex
-continue
-continue
-continue
-disable breakpoint 2
-continue
-continue
-enable breakpoint 2
-continue
-disable breakpoint 1
-disable breakpoint 2
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1333 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1333
deleted file mode 100644
index 9b02d823e..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1333
+++ /dev/null
@@ -1,13 +0,0 @@
-display info infoset
-set infosetLines 1
-break element.cell
-continue
-set infosetLines 4
-continue
-set infosetLines 10
-continue
-set infosetLines -900
-continue
-disable breakpoint 1
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1334 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1334
deleted file mode 100644
index e7b0d25ce..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1334
+++ /dev/null
@@ -1,8 +0,0 @@
-display info bitPosition
-display info data
-break element.cell
-continue
-continue
-continue
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1337 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1337
deleted file mode 100644
index 7f694f9b3..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1337
+++ /dev/null
@@ -1,9 +0,0 @@
-break element.cell
-display info childIndex
-display info infoset
-continue
-continue
-continue
-disable breakpoint 1
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1338 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1338
deleted file mode 100644
index cb9ddfcfc..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1338
+++ /dev/null
@@ -1,7 +0,0 @@
-break element.e3
-break element.e4
-display info discriminator
-continue
-continue
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1339 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1339
deleted file mode 100644
index eaaf7f0f8..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1339
+++ /dev/null
@@ -1,5 +0,0 @@
-set removeHidden false
-display info infoset
-break element.g
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1340 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1340
deleted file mode 100644
index a647e7098..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1340
+++ /dev/null
@@ -1,6 +0,0 @@
-break element.cell
-display info path
-continue
-delete breakpoint 1
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1382 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1382
deleted file mode 100644
index 65aa0e688..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1382
+++ /dev/null
@@ -1,12 +0,0 @@
-break element.cell
-continue
-info data
-set dataLength 2
-info data
-set dataLength -938
-info data
-set wrapLength 2
-info data
-disable breakpoint 1
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1591 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1591
deleted file mode 100644
index 6223fa103..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1591
+++ /dev/null
@@ -1,2 +0,0 @@
-garbage
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1602 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1602
deleted file mode 100644
index f10c84cb9..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1602
+++ /dev/null
@@ -1,5 +0,0 @@
-set removeHidden true
-display info infoset
-break g
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1863 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1863
deleted file mode 100644
index 229e2d105..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/1863
+++ /dev/null
@@ -1,8 +0,0 @@
-display info groupIndex
-break element.price
-break element.comment
-continue
-continue
-continue
-continue
-continue
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/982 b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/982
deleted file mode 100644
index a072c2312..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/982
+++ /dev/null
@@ -1,2 +0,0 @@
-continue
-quit
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output1.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output1.txt
deleted file mode 100644
index df2733dd7..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output1.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-<tns:matrix xmlns:tns="http://www.example.org/example1/">
-  <tns:row>
-    <tns:cell>0</tns:cell>
-    <tns:cell>1</tns:cell>
-    <tns:cell>2</tns:cell>
-  </tns:row>
-</tns:matrix>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output10.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output10.txt
deleted file mode 100644
index f3a797c3d..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output10.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-<rabbitHole>
-  <nestSequence>
-    <nest>test</nest>
-  </nestSequence>
-</rabbitHole>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output11.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output11.txt
deleted file mode 100644
index 820c05ceb..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output11.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-<tns:row xmlns:tns="http://example.com">
-  <tns:cell>0</tns:cell>
-  <tns:cell>99</tns:cell>
-  <tns:cell>-2</tns:cell>
-</tns:row>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output12.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output12.txt
deleted file mode 100644
index 17df74c86..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output12.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-<tns:row2 xmlns:tns="http://example.com">
-  <cell>-9</cell>
-  <cell>-2</cell>
-  <cell>-8</cell>
-</tns:row2>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output13.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output13.txt
deleted file mode 100644
index 0f4392e2d..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output13.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-[Pass] escape_entry4_1
-[Pass] escape_entry4_2
-[Pass] escape_entry4_3
-[Pass] escape_entry4_4
-[Pass] escape_entry4_5
-[Pass] escape_entry4_6
-[Pass] escape_entry4_7
-[Pass] escape_entry4_8
-[Pass] escape_entry4_9
-
-Total: 9, Pass: 9, Fail: 0, Not Found: 0
\ No newline at end of file
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output14.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output14.txt
deleted file mode 100644
index ab97ad2d4..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output14.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-escape_entry4_1
-escape_entry4_10
-escape_entry4_11
-escape_entry4_12
-escape_entry4_13
-escape_entry4_14
-escape_entry4_15
-escape_entry4_16
-escape_entry4_17
-escape_entry4_18
-escape_entry4_19
-escape_entry4_2
-escape_entry4_20
-escape_entry4_21
-escape_entry4_22
-escape_entry4_23
-escape_entry4_24
-escape_entry4_25
-escape_entry4_26
-escape_entry4_27
-escape_entry4_3
-escape_entry4_4
-escape_entry4_5
-escape_entry4_6
-escape_entry4_7
-escape_entry4_8
-escape_entry4_9
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output15.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output15.txt
deleted file mode 100644
index bab9a2876..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output15.txt
+++ /dev/null
@@ -1,90 +0,0 @@
-[Pass] escape_entry1
-[Pass] escape_entry10
-[Pass] escape_entry11
-[Pass] escape_entry12
-[Pass] escape_entry2
-[Pass] escape_entry2_1
-[Pass] escape_entry2_10
-[Pass] escape_entry2_11
-[Pass] escape_entry2_12
-[Pass] escape_entry2_13
-[Pass] escape_entry2_14
-[Pass] escape_entry2_15
-[Pass] escape_entry2_16
-[Pass] escape_entry2_17
-[Pass] escape_entry2_18
-[Pass] escape_entry2_2
-[Pass] escape_entry2_3
-[Pass] escape_entry2_4
-[Pass] escape_entry2_5
-[Pass] escape_entry2_6
-[Pass] escape_entry2_7
-[Pass] escape_entry2_8
-[Pass] escape_entry2_9
-[Pass] escape_entry3
-[Pass] escape_entry3_1
-[Pass] escape_entry3_10
-[Pass] escape_entry3_11
-[Pass] escape_entry3_12
-[Pass] escape_entry3_13
-[Pass] escape_entry3_14
-[Pass] escape_entry3_15
-[Pass] escape_entry3_16
-[Pass] escape_entry3_17
-[Pass] escape_entry3_18
-[Pass] escape_entry3_19
-[Pass] escape_entry3_2
-[Pass] escape_entry3_20
-[Pass] escape_entry3_21
-[Pass] escape_entry3_22
-[Pass] escape_entry3_23
-[Pass] escape_entry3_24
-[Pass] escape_entry3_25
-[Pass] escape_entry3_26
-[Pass] escape_entry3_27
-[Pass] escape_entry3_28
-[Pass] escape_entry3_29
-[Pass] escape_entry3_3
-[Pass] escape_entry3_30
-[Pass] escape_entry3_31
-[Pass] escape_entry3_4
-[Pass] escape_entry3_5
-[Pass] escape_entry3_6
-[Pass] escape_entry3_7
-[Pass] escape_entry3_8
-[Pass] escape_entry3_9
-[Pass] escape_entry4
-[Pass] escape_entry4_1
-[Pass] escape_entry4_10
-[Pass] escape_entry4_11
-[Pass] escape_entry4_12
-[Pass] escape_entry4_13
-[Pass] escape_entry4_14
-[Pass] escape_entry4_15
-[Pass] escape_entry4_16
-[Pass] escape_entry4_17
-[Pass] escape_entry4_18
-[Pass] escape_entry4_19
-[Pass] escape_entry4_2
-[Pass] escape_entry4_20
-[Pass] escape_entry4_21
-[Pass] escape_entry4_22
-[Pass] escape_entry4_23
-[Pass] escape_entry4_24
-[Pass] escape_entry4_25
-[Pass] escape_entry4_26
-[Pass] escape_entry4_27
-[Pass] escape_entry4_3
-[Pass] escape_entry4_4
-[Pass] escape_entry4_5
-[Pass] escape_entry4_6
-[Pass] escape_entry4_7
-[Pass] escape_entry4_8
-[Pass] escape_entry4_9
-[Pass] escape_entry5
-[Pass] escape_entry6
-[Pass] escape_entry7
-[Pass] escape_entry8
-[Pass] escape_entry9
-
-Total: 88, Pass: 88, Fail: 0, Not Found: 0
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output16.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output16.txt
deleted file mode 100644
index 64ee4e8f6..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output16.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-byte_entities_6_01
-byte_entities_6_02
-byte_entities_6_03
-byte_entities_6_04
-byte_entities_6_05
-byte_entities_6_06
-byte_entities_6_07
-byte_entities_6_08
-byte_entities_6_10
-dataDumpEncoding
-doubleNLseparator
-doubleNLterminator
-emptyStringEntityInitiator_01
-emptyStringEntityInitiator_02
-emptyStringEntityInitiator_03
-emptyStringEntityTermInComplex_01
-emptyStringEntityTermInComplex_02
-emptyStringEntityTermInExpressionDelimited_01
-emptyStringEntityTermInExpression_01
-emptyStringEntityTermInExpression_02
-entityAndNonMix_01
-entityAndNonMix_02
-entityAndNonMix_03
-entityAndNonMix_04
-errorEncoding
-text_entities_6_02
-text_entities_6_03
-text_entities_6_03b
-text_entities_6_04
-whitespace_01
-whitespace_02
-whitespace_03
-whitespace_04
-whitespace_05
-whitespace_06
-whitespace_07
-whitespace_08
-whitespace_09
-whitespace_10
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output1_nopretty.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output1_nopretty.txt
deleted file mode 100644
index 0ba364a3b..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output1_nopretty.txt
+++ /dev/null
@@ -1 +0,0 @@
-<tns:matrix xmlns:tns="http://www.example.org/example1/"><tns:row><tns:cell>0</tns:cell><tns:cell>1</tns:cell><tns:cell>2</tns:cell></tns:row></tns:matrix>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output2.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output2.txt
deleted file mode 100644
index ae24f2a6f..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output2.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-<tns:matrix xmlns:tns="http://www.example.org/example1/">
-  <tns:row>
-    <tns:cell>0</tns:cell>
-    <tns:cell>1</tns:cell>
-    <tns:cell>2</tns:cell>
-    <tns:cell>3</tns:cell>
-  </tns:row>
-</tns:matrix>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output3.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output3.txt
deleted file mode 100644
index c1b2eab1c..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output3.txt
+++ /dev/null
@@ -1 +0,0 @@
-Total: 4, Pass: 2, Fail: 0, Not Found: 2
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output4.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output4.txt
deleted file mode 100644
index 52a8fa73c..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output4.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-<address>
-  <houseNumber>118</houseNumber>
-  <street>Ridgewood Circle</street>
-  <city>Rochester</city>
-  <state>NY</state>
-</address>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output5.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output5.txt
deleted file mode 100644
index 4d898eb4d..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output5.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-<base:base xmlns:base="http://baseSchema.com">
-  <a08:aElem xmlns:a08="http://a08.com">random</a08:aElem>
-  <a08:aElem xmlns:a08="http://a08.com">data</a08:aElem>
-  <a08:aElem xmlns:a08="http://a08.com">should</a08:aElem>
-  <a08:aElem xmlns:a08="http://a08.com">work </a08:aElem>
-</base:base>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output6.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output6.txt
deleted file mode 100644
index 993765042..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output6.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-<tns:matrix xmlns:tns="target">
-  <row>
-    <cell>0</cell>
-    <cell>1</cell>
-    <cell>2</cell>
-    <cell>3</cell>
-    <cell>4</cell>
-  </row>
-  <row>
-    <cell>5</cell>
-    <cell>6</cell>
-    <cell>7</cell>
-    <cell>8</cell>
-  </row>
-  <row>
-    <cell>10</cell>
-    <cell>11</cell>
-    <cell>12</cell>
-    <cell>13</cell>
-    <cell>14</cell>
-  </row>
-</tns:matrix>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output8.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output8.txt
deleted file mode 100644
index ed53711b3..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output8.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-<ABC>
-  <Container>
-    <a>a</a>
-  </Container>
-  <Container>
-    <b>b</b>
-  </Container>
-  <Container>
-    <c>c</c>
-  </Container>
-  <Container>
-    <a>a</a>
-  </Container>
-  <Container>
-    <b>b</b>
-  </Container>
-  <Container>
-    <c>c</c>
-  </Container>
-  <Container>
-    <a>a</a>
-  </Container>
-  <Container>
-    <b>b</b>
-  </Container>
-  <Container>
-    <c>c</c>
-  </Container>
-</ABC>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output9.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output9.txt
deleted file mode 100644
index dc021e8cd..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output9.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-<base14:rabbitHole xmlns:a14="http://a14.com" xmlns:b14="http://b14.com" xmlns:base14="http://baseSchema.com">
-  <a14:nestSequence>
-    <b14:nest>test</b14:nest>
-  </a14:nestSequence>
-</base14:rabbitHole>
\ No newline at end of file
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output_DFDL-714.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output_DFDL-714.txt
deleted file mode 100644
index 8ef75e7cc..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output_DFDL-714.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-<tns:elem xmlns:tns="http://baseSchema.com">
-  <tns:content xmlns:tns="http://complexSchema.com">Hello World </tns:content>
-</tns:elem>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_01.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_01.txt
deleted file mode 100644
index bdf485597..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_01.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-<test_01 xmlns="http://example.com">
-  <a>
-    <b>1</b>
-    <c xmlns="">2</c>
-  </a>
-  <s xmlns="">1</s>
-</test_01>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_02.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_02.txt
deleted file mode 100644
index 6cfcf4e94..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_02.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-<test_02 xmlns="http://example.com">
-  <a>
-    <b>1</b>
-    <c xmlns="">2</c>
-  </a>
-  <s xmlns="">1</s>
-</test_02>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_03.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_03.txt
deleted file mode 100644
index feade8477..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_03.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-<test_03 xmlns="http://example.com">
-  <a>
-    <b>1</b>
-    <c xmlns="">2</c>
-  </a>
-  <s xmlns="">2</s>
-</test_03>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_04.txt b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_04.txt
deleted file mode 100644
index c46b6105e..000000000
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/unqualified_path_step_04.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-<test_04 xmlns="http://example.com">
-  <a>
-    <b>1</b>
-    <c xmlns="">2</c>
-  </a>
-  <s xmlns="">2</s>
-</test_04>
diff --git a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/xcatalog_invalid.xml b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/xcatalog_invalid.xml
index 07a2e1143..93bf22ed2 100644
--- a/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/xcatalog_invalid.xml
+++ b/daffodil-cli/src/it/resources/org/apache/daffodil/CLI/xcatalog_invalid.xml
@@ -17,5 +17,5 @@
 -->
 
 <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
-  <uri name="http://example.com/xcatalog" uri="file:/this/path/does/not/exist" />
+  <uri name="http://example.com/xcatalog" uri="file:/this/path/does/not/exist/non_existent_file.xml" />
 </catalog>
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/CLI/Util.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/CLI/Util.scala
index 0442d0fd6..ece7288eb 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/CLI/Util.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/CLI/Util.scala
@@ -17,183 +17,503 @@
 
 package org.apache.daffodil.CLI
 
-import org.apache.daffodil.util.Misc
-import net.sf.expectit.ExpectBuilder
+import java.io.File
+import java.io.InputStream
+import java.io.OutputStream
+import java.io.PipedInputStream
+import java.io.PipedOutputStream
+import java.io.PrintStream
+import java.lang.ProcessBuilder
+import java.math.BigInteger
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.security.MessageDigest
+import java.util.concurrent.TimeUnit
+
+import scala.collection.JavaConverters._
+import scala.collection.mutable
+
+import com.fasterxml.jackson.core.io.JsonStringEncoder
+
 import net.sf.expectit.Expect
+import net.sf.expectit.ExpectBuilder
+import net.sf.expectit.Result
 import net.sf.expectit.filter.Filters.replaceInString
+import net.sf.expectit.matcher.Matcher
 import net.sf.expectit.matcher.Matchers.contains
-import org.apache.daffodil.Main.ExitCode
 
-import java.nio.file.Paths
-import java.io.{File, PrintWriter}
-import java.util.concurrent.TimeUnit
-import org.apache.daffodil.xml.XMLUtils
-import org.junit.Assert.fail
+import org.apache.commons.io.FileUtils
 
-object Util {
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.core.appender.OutputStreamAppender
+import org.apache.logging.log4j.core.config.AbstractConfiguration
+import org.apache.logging.log4j.core.config.ConfigurationSource
+import org.apache.logging.log4j.core.config.Configurator
+import org.apache.logging.log4j.core.layout.PatternLayout
 
-  //val testDir = "daffodil-cli/src/it/resources/org/apache/daffodil/CLI/"
-  val testDir = "/org/apache/daffodil/CLI/"
-  val outputDir = testDir + "output/"
+import org.junit.Assert.assertEquals
 
-  val isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows")
+import org.apache.daffodil.Main
+import org.apache.daffodil.Main.ExitCode
 
-  val dafRoot = sys.env.getOrElse("DAFFODIL_HOME", ".")
+object Util {
 
-  def daffodilPath(dafRelativePath: String): String = {
-    XMLUtils.slashify(dafRoot) + dafRelativePath
-  }
+  private val isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows")
 
-  val binPath = Paths.get(dafRoot, "daffodil-cli", "target", "universal", "stage", "bin", String.format("daffodil%s", (if (isWindows) ".bat" else ""))).toString()
+  private val daffodilRoot = sys.env.getOrElse("DAFFODIL_HOME", ".")
 
-  def getExpectedString(filename: String, convertToDos: Boolean = false): String = {
-    val rsrc = Misc.getRequiredResource(outputDir + filename)
-    val is = rsrc.toURL.openStream()
-    val source = scala.io.Source.fromInputStream(is)
-    val lines = source.mkString.trim()
-    source.close()
-    fileConvert(lines)
+  private val daffodilBinPath = {
+    val ext = if (isWindows) ".bat" else ""
+    Paths.get(daffodilRoot, s"daffodil-cli/target/universal/stage/bin/daffodil$ext")
   }
 
-  def start(cmd: String, envp: Map[String, String] = Map.empty[String, String], timeout: Long = 30): Expect = {
-    val spawnCmd = if (isWindows) {
-      "cmd /k" + cmdConvert(cmd)
-    } else {
-      "/bin/bash"
-    }
-
-    getShell(cmd, spawnCmd, envp, timeout)
+  /**
+   * Convert the daffodilRoot + parameter to a java Path. The string
+   * parameter should contain unix path sparators and it will be interpreted
+   * correctly regardless of operating system. When converted to a string to
+   * send to the CLI, it will use the correct line separator for the
+   * operating system
+   */
+  def path(string: String): Path = {
+    Paths.get(daffodilRoot, string)
   }
 
-  // This function will be used if you are providing two separate commands
-  // and doing the os check on the 'front end' (not within this utility class)
-  def startNoConvert(cmd: String, envp: Map[String, String] = Map.empty[String, String], timeout: Long = 30): Expect = {
-    val spawnCmd = if (isWindows) {
-      "cmd /k" + cmd
-    } else {
-      "/bin/bash"
-    }
+  def devNull(): String = if (isWindows) "NUL" else "/dev/null"
 
-    return getShell(cmd, spawnCmd, envp = envp, timeout = timeout)
-  }
-
-  // Return a shell object with two streams
-  // The inputStream will be at index 0
-  // The errorStream will be at index 1
-  def getShell(cmd: String, spawnCmd: String, envp: Map[String, String] = Map.empty[String, String], timeout: Long): Expect = {
-    val newEnv = sys.env ++ envp
-
-    val envAsArray = newEnv.toArray.map { case (k, v) => k + "=" + v }
-    val process = Runtime.getRuntime().exec(spawnCmd, envAsArray)
-    val shell = new ExpectBuilder()
-      .withInputs(process.getInputStream(), process.getErrorStream())
-      .withInputFilters(replaceInString("\r\n", "\n"))
-      .withOutput(process.getOutputStream())
-      .withEchoOutput(System.out)
-      .withEchoInput(System.out)
-      .withTimeout(timeout, TimeUnit.SECONDS)
-      .withExceptionOnFailure()
-      .build();
-    if (!isWindows) {
-      shell.send(cmd)
+  def md5sum(path: Path): String = {
+    val md = MessageDigest.getInstance("MD5")
+    val buffer = new Array[Byte](8192)
+    val stream = Files.newInputStream(path)
+    var read = 0
+    while ({read = stream.read(buffer); read} > 0) {
+      md.update(buffer, 0, read)
     }
-    return shell
+    val md5sum = md.digest()
+    val bigInt = new BigInteger(1, md5sum)
+    bigInt.toString(16)
   }
 
-  def cmdConvert(str: String): String = {
-    if (isWindows)
-      str.replaceAll("/", "\\\\")
-    else
-      str
+  /**
+   * Create a temporary file in /tmp/daffodil/, call a user provided function
+   * passing in the Path to that new file, and delete the file when the
+   * function returns.
+   */
+  def withTempFile(f: (Path) => Unit) : Unit = withTempFile(null, f)
+
+  /**
+   * Create a temporary file in /tmp/daffodil/ with a givin suffix, call a user
+   * provided function passing in the Path to that new file, and delete the
+   * file when the function returns.
+   */
+  def withTempFile(suffix: String, f: (Path) => Unit): Unit = {
+    val tempRoot = Paths.get(System.getProperty("java.io.tmpdir"), "daffodil")
+    Files.createDirectories(tempRoot)
+    val tempFile = Files.createTempFile(tempRoot, "daffodil-", suffix)
+    try {
+      f(tempFile)
+    } finally {
+      tempFile.toFile.delete()
+    }
   }
 
-  def fileConvert(str: String): String = {
-    val newstr = str.replaceAll("\r\n", "\n")
-    return newstr
+  /**
+   * Create a temporary directory in /tmp/daffodil/, call a user provided
+   * function passing in the Path to that new directory, and delete the
+   * directory and all of its contents when the function returns
+   */
+  def withTempDir(f: (Path) => Unit): Unit = {
+    val tempRoot = Paths.get(System.getProperty("java.io.tmpdir"), "daffodil")
+    Files.createDirectories(tempRoot)
+    val tempDir = Files.createTempDirectory(tempRoot, "daffodil-")
+    try {
+      f(tempDir)
+    } finally {
+      FileUtils.deleteDirectory(tempDir.toFile)
+    }
   }
 
-  def echoN(str: String): String = {
-    if (isWindows) {
-      "echo|set /p=" + str
-    } else {
-      "echo -n " + str
+  /**
+   * Set a system property using a provided key, value tuple, call a user
+   * provided function, and reset or clear the property when the function
+   * returns.
+   */
+  def withSysProp(keyVal: (String, String))(f: => Unit): Unit = {
+    val key = keyVal._1
+    val newVal = keyVal._2
+    val oldVal = System.setProperty(key, newVal)
+    try {
+      f
+    } finally {
+      if (oldVal == null) {
+        System.clearProperty(key)
+      } else {
+        System.setProperty(key, oldVal)
+      }
     }
   }
 
-  def devNull(): String = {
-    if (isWindows) {
-      "NUL"
-    } else {
-      "/dev/null"
+  /**
+   * Run a CLI test.
+   *
+   * Runs CLI logic using the provided arguments and classpath, creates a
+   * CLITester so that the user can send input and validate output, and
+   * verifies the expected exit code.
+   *
+   * For performance reasons, this defaults to running the CLI in a new thread
+   * unless the classpaths parameter is nonempty or the fork parameter is set to
+   * true. Otherwise a new process is spawned.
+   *
+   * @param args arguments to pass to the CLI. This should not include the
+   *   daffodil binary
+   * @param classpaths sequence of paths to add to the classpath. If non-empty,
+   *   runs the CLI in a new process instead of a thread and will likely decrease
+   *   performance
+   * @param fork if true, forces the the CLI to run in a new process
+   * @param timeout how long to wait, in seconds, for the CLI to exit after the
+   *   testFunc has returned. Also how long to wait for individual expect
+   *   operations in the CLITester
+   * @param debug if true, prints arguments and classpath information to
+   *   stdout. Also echos all CLITester input and output to stdout.
+   * @param testFunc function to call to send input to the CLI and validate
+   *   output from CLI stdout/stderr
+   * @param expectedExitCode the expected exit code of the CLI
+   *
+   * @throws AssertionError if the actual exit code does not match the expected exit code
+   * @throws ExpectIOException if a CLITester expect validation operation fails
+   */
+  def runCLI
+    (args: Array[String], classpaths: Seq[Path] = Seq(), fork: Boolean = false, timeout: Int = 10, debug: Boolean = false)
+    (testFunc: (CLITester) => Unit)
+    (expectedExitCode: ExitCode.Value): Unit = {
+
+    val (toIn, fromOut, fromErr, threadOrProc: Either[CLIThread, Process]) =
+      if (classpaths.nonEmpty || fork) {
+        // spawn a new process to run Daffodil, needed if a custom classpath is
+        // defined or if the caller explicitly wants to fork
+        val processBuilder = new ProcessBuilder()
+
+        if (classpaths.nonEmpty) {
+          val classpath = classpaths.mkString(File.pathSeparator)
+          if (debug) System.out.println(s"DAFFODIL_CLASSPATH=$classpath")
+          processBuilder.environment().put("DAFFODIL_CLASSPATH", classpath)
+        }
+
+        val cmd = daffodilBinPath.toString +: args
+        if (debug) System.out.println(cmd.mkString(" "))
+        processBuilder.command(cmd.toList.asJava)
+
+        val process = processBuilder.start()
+
+        val toIn = process.getOutputStream()
+        val fromOut = process.getInputStream()
+        val fromErr = process.getErrorStream()
+        (toIn, fromOut, fromErr, Right(process))
+      } else {
+        // create a new thread for the CLI test to run, using piped
+        // input/output streams to connect the thread and the CLITester
+        val in = new PipedInputStream()
+        val toIn = new PipedOutputStream(in)
+
+        val out = new PipedOutputStream()
+        val fromOut = new PipedInputStream(out)
+
+        val err = new PipedOutputStream()
+        val fromErr = new PipedInputStream(err)
+
+        if (debug) System.out.println("daffodil " + args.mkString(" "))
+
+        val thread = new CLIThread(args, in, out, err)
+        thread.start()
+        (toIn, fromOut, fromErr, Left(thread))
+      }
+
+    val eb = new ExpectBuilder()
+    eb.withOutput(toIn)
+    eb.withInputs(fromOut, fromErr)
+    eb.withInputFilters(replaceInString("\r\n", "\n"))
+    eb.withTimeout(timeout, TimeUnit.SECONDS)
+    eb.withExceptionOnFailure()
+    if (debug) {
+      eb.withEchoOutput(System.out)
+      eb.withEchoInput(System.out)
+    }
+    val expect = eb.build()
+    val tester = new CLITester(expect, toIn)
+
+    try {
+      testFunc(tester)
+    } finally {
+      threadOrProc match {
+        case Left(thread) => thread.join(timeout * 1000)
+        case Right(process) => process.waitFor(timeout, TimeUnit.SECONDS)
+      }
+      expect.close()
+      toIn.close()
+      fromOut.close()
+      fromErr.close()
     }
-  }
 
-  def makeMultipleCmds(cmds: Array[String]): String = {
-    if (isWindows) {
-      cmds.mkString(" & ")
-    } else {
-      cmds.mkString("; ")
+    val actualExitCode = threadOrProc match {
+      case Left(thread) => thread.exitCode
+      case Right(process) => ExitCode(process.exitValue)
     }
+    assertEquals("Incorrect exit code,", expectedExitCode, actualExitCode)
   }
 
-  def md5sum(blob_path: String): String = {
-    if (isWindows) {
-      String.format("certutil -hashfile %s MD5", blob_path)
-    } else {
-      String.format("md5sum %s", blob_path)
+  /**
+   * A class to run the CLI in a thread instead of a new process, given the
+   * arguments to use (excluding the daffodil binary) and streams to use for
+   * stdin/out/err.
+   */
+  private class CLIThread(args: Array[String], in: InputStream, out: OutputStream, err: OutputStream) extends Thread {
+    var exitCode = ExitCode.Failure
+
+    override def run(): Unit = {
+      val psOut = new PrintStream(out)
+      val psErr = new PrintStream(err)
+
+      // configure the CLI and log4j to use our custom streams, nothing should
+      // actually use stdin/stdout/stderr
+      Main.setInputOutput(in, psOut, psErr)
+      configureLog4j(psErr)
+
+      exitCode = Main.run(args)
     }
-  }
 
-  def rmdir(path: String): String = {
-    if (Util.isWindows)
-      String.format("rmdir /Q /S %s", path)
-    else
-      String.format("rm -rf %s", path)
+    /**
+     * By default log4j outputs to stderr. This changes that so it writes to a
+     * provided PrintStream which is connected to the CLITester, allowing tests
+     * to expect content written by log4j. This also defines the same pattern
+     * used by the CLI from the daffodil-cli/src/conf/log4j2.xml config
+     * file--we duplicate it here because the normal log4j config file targets
+     * stderr while we need to target the provided PrintStream (which can only
+     * be defined programatically). It was also found to be too difficult to
+     * load the config file and mutate the log4j configuration to write to this
+     * PrintStream instead of stderr.
+     */
+    private def configureLog4j(ps: PrintStream): Unit = {
+      val config = new AbstractConfiguration(null, ConfigurationSource.NULL_SOURCE) {
+        override def doConfigure(): Unit = {
+          val appenderName = "DaffodilCli"
+
+          val layout = PatternLayout.newBuilder()
+            .withPattern("[%p{lowerCase=true}] %m%n")
+            .withConfiguration(this)
+            .build()
+
+          val appenderBuilder: OutputStreamAppender.Builder[_] = OutputStreamAppender.newBuilder()
+          appenderBuilder.setName(appenderName)
+          appenderBuilder.setLayout(layout)
+          appenderBuilder.setTarget(ps)
+          appenderBuilder.setConfiguration(this)
+          val appender = appenderBuilder.build()
+
+          val rootLogger = getRootLogger()
+          rootLogger.setLevel(Level.WARN);
+          rootLogger.addAppender(appender, null, null)
+        }
+      }
+      Configurator.reconfigure(config)
+    }
   }
 
-  def cat(str: String): String = {
-    if (isWindows) {
-      "type " + str
-    } else {
-      "cat " + str
+  /**
+   * Wrapper around Expect to make integration tests less verbose. It also
+   * supports closing the mimicked stdin input stream (via the closeInput()
+   * function or the inputDone parameter to the send*() functions), which is
+   * sometimes needed since Daffodil may need to receive an EOF before it can
+   * finish parsing.
+   */
+  private class CLITester(expect: Expect, toIn: OutputStream) {
+
+    /**
+     * Close stdin, triggering an EOF.
+     */
+    def closeInput(): Unit = { toIn.close() }
+
+    /**
+     * Write a string to stdin. This does not incluede trailing newline. If
+     * inputDone is true, close stdin afterwards.
+     */
+    def send(string: String, inputDone: Boolean = false): Unit = {
+      expect.send(string)
+      if (inputDone) closeInput()
     }
-  }
 
-  def newTempFile(filePrefix: String, fileSuffix: String, optFileContents: Option[String] = None): File = {
-    val inputFile = File.createTempFile(filePrefix, fileSuffix)
-    inputFile.deleteOnExit
-    if (optFileContents.nonEmpty) {
-      val contents = optFileContents.get
-      val pw = new PrintWriter(inputFile)
-      pw.write(contents)
-      pw.close
+    /**
+     * Write a string to stdin with a trailing newline. If inputDone is true,
+     * close stdin afterwards.
+     */
+    def sendLine(string: String, inputDone: Boolean = false): Unit = {
+      expect.sendLine(string)
+      if (inputDone) closeInput()
     }
-    inputFile
-  }
 
-  def expectExitCode(expectedExitCode: ExitCode.Value, shell: Expect): Unit = {
-    val expectedCode = expectedExitCode.id
+    /**
+     * Write an entire byte array to stdin. If inputDone is true, close stdin
+     * afterwards.
+     */
+    def sendBytes(bytes: Array[Byte], inputDone: Boolean = false): Unit = {
+      expect.sendBytes(bytes)
+      if (inputDone) closeInput()
+    }
 
-    val keyWord = "EXITCODE:"
+    /**
+     * Write a file to stdin. If inputDone is true, close stdin afterwards.
+     */
+    def sendFile(path: Path, inputDone: Boolean = false): Unit = {
+      val chunkSize = 8192
+      val buffer = new Array[Byte](chunkSize)
+      val stream = Files.newInputStream(path)
+      var read = 0
+      while ({read = stream.read(buffer); read} > 0) {
+        if (read == chunkSize) {
+          expect.sendBytes(buffer)
+        } else {
+          // The expect.sendBytes function does not have parameters to send a
+          // subset of an array, it just sends the whole array. So we need to
+          // trim it down to the actual read size and send that
+          val smaller = new Array[Byte](read)
+          buffer.copyToArray(smaller, 0, read)
+          expect.sendBytes(smaller)
+        }
+      }
+      if (inputDone) closeInput()
+    }
 
-    //Escaped characters ^| for windows and \\! for linux makes the echo outputs different text than the command,
-    //That way the expect function can tell the difference.
-    val exitCodeCmd = "echo " + keyWord + (if (Util.isWindows) "^|%errorlevel%" else "\\!$?")
-    val exitCodeExpectation = keyWord + (if (Util.isWindows) "|" else "!")
+    def expect(matcher: Matcher[_]): Result = expect.expect(matcher)
+    def expect(string: String): Result = expect.expect(contains(string))
 
-    shell.sendLine(exitCodeCmd)
-    shell.expect(contains(exitCodeExpectation))
+    def expectErr(matcher: Matcher[_]): Result = expect.expectIn(1, matcher)
+    def expectErr(string: String): Result = expect.expectIn(1, contains(string))
+  }
 
-    val sExitCode = shell.expect(contains("\n")).getBefore().trim()
-    val actualInt = Integer.parseInt(sExitCode)
+  /**
+   * Escapes a string that is expected to be a json string
+   */
+  def jsonEscape(string: String): String = {
+    val chars = JsonStringEncoder.getInstance().quoteAsString(string)
+    new String(chars)
+  }
 
-    if (actualInt != expectedCode) {
-      val expectedExitCodeName = expectedExitCode.toString
-      val actualExitCodeName = ExitCode.values.find { _.id == actualInt }.map { _.toString }.getOrElse("Unknown")
-      val failMessage = "Exit code %s expected (%s), but got %s (%s) instead.".format(
-        expectedCode, expectedExitCodeName, actualInt, actualExitCodeName)
-      fail(failMessage)
+  /**
+   * This "args" string interpoloator makes it easy to create an Array[String]
+   * used for CLI arguments. Only spaces in the "format" string are split on.
+   * Spaces in an expressions in the format string are not split. For example
+   *
+   *   args"parse -s $schema $input".split(" ")
+   *
+   * Becomes someething like this:
+   *
+   *   Array("parse", "-s", "path/to/schema.dfdl.xsd", "path/to/input.bin")
+   *
+   * An alternative approach one might choose by using existing interpolators
+   * is something like this:
+   *
+   *   s"parse -s $schema $input".split(" ")
+   *
+   * This issue with this approach is that if the $schema or $input variables
+   * evaluate to something with string (which is not uncommon on some windows
+   * systems), then we end up splitting those files paths into separate
+   * arguments. This args interpolator ensures we don't split spaces that come
+   * from expressions.
+   *
+   * Note that quotes cannot be used to prevent splitting. For example, this
+   *
+   *   args"quotes do 'not prevent' splitting"
+   *
+   * Results in the following:
+   *
+   *   Array("quotes", "do", "'not", "prevent'", "splitting")
+   *
+   * To prevent splitting on a particular space, then expressions can be used,
+   * for example:
+   *
+   *   args"this ${"is split"} correctly"
+   *
+   * Which results in the following:
+   *
+   *   Array("this", "is split", "correctly")
+   *
+   * Note that this also handles concatenating expression correctly, for
+   * example:
+   *
+   *   args"some --arg=$root/$value"
+   *
+   * Results in
+   *
+   *   Array("some", "--arg=the/result/of/root/and/value")
+   *
+   */
+  implicit class ArgsHelper(val sc: StringContext) extends AnyVal {
+    def args(exprs: Any*): Array[String] = {
+      val strings = sc.parts.iterator
+      val expressions = exprs.iterator
+      val buf = mutable.ArrayBuffer[String]()
+
+      // regex to split on spaces, but using positive lookahead and lookbehind
+      // so the spaces end up in the array instead of being discarded. For
+      // example, using this regular expression to split this string:
+      //
+      //   "parse --schema foo.xsd input"
+      //
+      // Results in the following:
+      //
+      //   Array("parse", " ", "--schema", " ", "foo.xsd", " ", "input")
+      //
+      // This is necessary so that after we build the buf array, the spaces
+      // provide extra information about how to concatenate exprs and
+      // StringContexts
+      val regex = "((?= )|(?<= ))".r
+
+      buf.appendAll(regex.split(strings.next()))
+      while (strings.hasNext) {
+        buf.append(expressions.next().toString)
+        buf.appendAll(regex.split(strings.next()))
+      }
+
+      // At this point, we have an array that is mixed with arguments and
+      // single spaces, for example, this string
+      //
+      //   args"parse --schema=$bar/$baz input"
+      //
+      // Now looks like this:
+      //
+      //   Array("parse", " ", "--schema=", "bar/result", "/", "baz/result", " ", "input")
+      //
+      // We want to concatent any array elements that aren't separated by a
+      // space element and drop all the spaces. The following does this by
+      // accumulating args until we hit a space and adds that to the
+      // final array
+      val (res, lastAccumulated) = buf.foldLeft((mutable.ArrayBuffer[String](), "")) { case ((resArray, accumulated), curVal) =>
+        (accumulated, curVal) match {
+          case ("", " ") => {
+            // curVal is a space, but we have accumulated nothing. This means
+            // there were multiple spaces in a row. Just ignore this curVal
+            (resArray, "")
+          }
+          case (_, " ") => {
+            // curVal is a space and we have accumulated an argument. Append
+            // what we have accumulated to the array and reset the accumulated
+            // string--we will start accumulating for the next arg
+            (resArray :+ accumulated, "")
+          }
+          case _ => {
+            // curVal is not a space. Do not modify the array, but concatenate
+            // the curVal to what we've accumulated so far
+            (resArray, accumulated + curVal)
+          }
+        }
+      }
+      // if non-empty, the last thing we accumulated must be appended to the array
+      val args =
+        if (lastAccumulated != "") {
+          res :+ lastAccumulated
+        } else {
+          res
+        }
+      args.toArray
     }
-
   }
 }
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/blob/TestBlob.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/blob/TestBlob.scala
index a93b52470..31d7ee104 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/blob/TestBlob.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/blob/TestBlob.scala
@@ -17,12 +17,22 @@
 
 package org.apache.daffodil.blob
 
-import java.io.File
-import org.apache.daffodil.CLI.Util
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.eof
+import java.net.URI
+import java.nio.file.Files.exists
+import java.nio.file.Path
+import java.nio.file.Paths
+
+import org.apache.commons.io.FileUtils
+
+import org.junit.Test
+import org.junit.Assume.assumeTrue
+import org.junit.Assert.assertEquals
+
 import scala.io.Source
 
+import org.apache.daffodil.CLI.Util._
+import org.apache.daffodil.Main.ExitCode
+
 class TestBlob {
 
   /***
@@ -47,6 +57,25 @@ class TestBlob {
    * a comment above that test
    ***/
 
+  private def findInfosetBlob(path: Path): Path = {
+    val contents = Source.fromFile(path.toFile).mkString
+    val blob = contents.substring(contents.indexOf("file://")).takeWhile(_ != '<')
+    Paths.get(new URI(blob))
+  }
+
+  /**
+   * The CLI puts blobs in user.dir / "daffodil-blobs", which cannot be
+   * changed. This should wrap CLI runs so that the blob dir is deleted at the
+   * end
+   */
+  private def withBlobDir(f: => Unit): Unit = {
+    try {
+      f
+    } finally {
+      val blobDir = Paths.get(System.getProperty("user.dir"), "daffodil-blobs")
+      FileUtils.deleteDirectory(blobDir.toFile)
+    }
+  }
 
   /***
    * Command to generate blob file:
@@ -54,51 +83,26 @@ class TestBlob {
    * python gen_blob.py -s 1 -o 1MB.bin
    *
    ***/
-  /*@Test*/ def test_1MB_blob(): Unit = {
-
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/large_blob.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/1MB.bin")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-    val infosetFile = File.createTempFile("daffodil-1MB-", ".bin.xml")
-    val unparseFile = File.createTempFile("daffodil-1MB-", "bin.xml.bin")
-    infosetFile.deleteOnExit()
-    unparseFile.deleteOnExit()
-
-    val shell = Util.start("")
-
-    try {
-      // parse to a file
-      val cmdP = String.format("%s parse -s %s -o %s %s && echo success", Util.binPath, testSchemaFile, infosetFile, testInputFile)
-      shell.sendLine(cmdP)
-      shell.expect(contains("success"))
-
-      val infosetContents = Source.fromFile(infosetFile).mkString
-
-      // Use +7 to drop the 'file://' to get the path of the file
-      val generated_blob = infosetContents.substring(infosetContents.indexOf("file://") + 7).takeWhile(_ != '<')
-
-      // unparse to a file
-      val cmdU = String.format("%s unparse -s %s -o %s %s && echo success", Util.binPath, testSchemaFile, unparseFile, infosetFile)
-      shell.sendLine(cmdU)
-      shell.expect(contains("success"))
-
-      // Compare blobs
-      shell.sendLine(Util.md5sum(generated_blob))
-      shell.expect(contains("bc8f9d01382bf12248747cd6faecbc59"))
-
-      shell.sendLine(Util.md5sum(unparseFile.toString))
-      shell.expect(contains("72d1f935d7fff766d011757ae03d5b1d"))
-
-      // Clean up files
-      shell.sendLine(Util.rmdir("daffodil-blobs"))
-      infosetFile.delete()
-      unparseFile.delete()
-
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
+  @Test def test_1MB_blob(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/large_blob.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/1MB.bin")
+
+    assumeTrue("large test input file must be manually generated", exists(input))
+
+    withTempFile { infoset =>
+      withTempFile { unparse =>
+        withBlobDir {
+          runCLI(args"parse -s $schema -o $infoset $input") { cli =>
+          } (ExitCode.Success)
+
+          runCLI(args"unparse -s $schema -o $unparse $infoset") { cli =>
+          } (ExitCode.Success)
+
+          val blob = findInfosetBlob(infoset)
+          assertEquals("bc8f9d01382bf12248747cd6faecbc59", md5sum(blob))
+          assertEquals("72d1f935d7fff766d011757ae03d5b1d", md5sum(unparse))
+        }
+      }
     }
   }
 
@@ -108,52 +112,26 @@ class TestBlob {
    * python gen_blob.py -s 2049 -o 2049MB.bin
    *
    ***/
-  /*@Test*/ def test_2GB_blob(): Unit = {
-
-    val DAFFODIL_JAVA_OPTS = Map("DAFFODIL_JAVA_OPTS" -> "-Xms256m -Xmx512m")
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/large_blob.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/2049MB.bin")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-    val infosetFile = File.createTempFile("daffodil-2GB-", ".bin.xml")
-    val unparseFile = File.createTempFile("daffodil-2GB-", "bin.xml.bin")
-    infosetFile.deleteOnExit()
-    unparseFile.deleteOnExit()
-
-    val shell = Util.start("", envp = DAFFODIL_JAVA_OPTS)
-
-    try {
-      // Parse to a file
-      val cmdP = String.format("%s parse -s %s -o %s %s && echo success", Util.binPath, testSchemaFile, infosetFile, testInputFile)
-      shell.sendLine(cmdP)
-      shell.expect(contains("success"))
-
-      val infosetContents = Source.fromFile(infosetFile).mkString
-
-      // Use +7 to drop the 'file://' to get the path of the file
-      val generated_blob = infosetContents.substring(infosetContents.indexOf("file://") + 7).takeWhile(_ != '<')
-
-      // unparse to a file
-      val cmdU = String.format("%s unparse -s %s -o %s %s && echo success", Util.binPath, testSchemaFile, unparseFile, infosetFile)
-      shell.sendLine(cmdU)
-      shell.expect(contains("success"))
-
-      // Compare blobs
-      shell.sendLine(Util.md5sum(generated_blob))
-      shell.expect(contains("c5675d3317725595d128af56a624c49f"))
-
-      shell.sendLine(Util.md5sum(unparseFile.toString))
-      shell.expect(contains("2435c33e55aae043fc9b28f38f5cc2e9"))
-
-      // Clean up files
-      shell.sendLine(Util.rmdir("daffodil-blobs"))
-      infosetFile.delete()
-      unparseFile.delete()
-
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
+  @Test def test_2GB_blob(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/large_blob.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/2049MB.bin")
+
+    assumeTrue("large test input file must be manually generated", exists(input))
+
+    withTempFile { infoset =>
+      withTempFile { unparse =>
+        withBlobDir {
+          runCLI(args"parse -s $schema -o $infoset $input", timeout = 120) { cli =>
+          } (ExitCode.Success)
+
+          runCLI(args"unparse -s $schema -o $unparse $infoset", timeout = 120) { cli =>
+          } (ExitCode.Success)
+
+          val blob = findInfosetBlob(infoset)
+          assertEquals("c5675d3317725595d128af56a624c49f", md5sum(blob))
+          assertEquals("2435c33e55aae043fc9b28f38f5cc2e9", md5sum(unparse))
+        }
+      }
     }
   }
 
@@ -165,29 +143,16 @@ class TestBlob {
    * python gen_blob.py -s 2049 -o 2049MB.bin
    *
    ***/
-  /*@Test*/ def test_blob_backtracking(): Unit = {
-
-    val DAFFODIL_JAVA_OPTS = Map("DAFFODIL_JAVA_OPTS" -> "-Xms256m -Xmx512m")
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/blob_backtracking.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/2049MB.bin")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("", envp = DAFFODIL_JAVA_OPTS)
+  @Test def test_blob_backtracking(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/blob_backtracking.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/2049MB.bin")
 
-    try {
-      // Execute Daffodil
-      val cmd = String.format("%s parse -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Attempted to backtrack too far"))
-
-      // Clean up blobs
-      shell.sendLine(Util.rmdir("daffodil-blobs"))
+    assumeTrue("large test input file must be manually generated", exists(input))
 
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
+    withBlobDir {
+      runCLI(args"parse -s $schema $input", timeout = 120) { cli =>
+        cli.expectErr("Attempted to backtrack too far")
+      } (ExitCode.ParseError)
     }
   }
 
@@ -199,29 +164,17 @@ class TestBlob {
    * python gen_blob.py -s 2049 -o 2049MB.bin
    *
    ***/
-  /*@Test*/ def test_blob_backtracking_streaming_fail(): Unit = {
+  @Test def test_blob_backtracking_streaming_fail(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/blob_backtracking.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/2049MB.bin")
 
-    val DAFFODIL_JAVA_OPTS = Map("DAFFODIL_JAVA_OPTS" -> "-Xms256m -Xmx512m")
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/blob_backtracking.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/2049MB.bin")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
+    assumeTrue("large test input file must be manually generated", exists(input))
 
-    val shell = Util.start("", envp = DAFFODIL_JAVA_OPTS)
-
-    try {
-      // Execute Daffodil
-      val cmd = String.format(Util.cat(testInputFile) + " | %s parse --stream -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Attempted to backtrack too far"))
-
-      // Clean up blobs
-      shell.sendLine(Util.rmdir("daffodil-blobs"))
-
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
+    withBlobDir {
+      runCLI(args"parse -s $schema", timeout = 120) { cli =>
+        cli.sendFile(input, inputDone = true)
+        cli.expectErr("Attempted to backtrack too far")
+      } (ExitCode.ParseError)
     }
   }
 
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/debugger/TestCLIDebugger.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/debugger/TestCLIDebugger.scala
index 4fd06fe8a..2b1829430 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/debugger/TestCLIDebugger.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/debugger/TestCLIDebugger.scala
@@ -19,1477 +19,1108 @@ package org.apache.daffodil.debugger
 
 import org.junit.Test
 
-import net.sf.expectit.matcher.Matchers.allOf
-import net.sf.expectit.matcher.Matchers.contains
+import java.nio.file.Files
+import java.nio.charset.StandardCharsets.UTF_8
+
 import net.sf.expectit.matcher.Matchers.regexp
-import net.sf.expectit.matcher.Matchers.times
-import org.apache.daffodil.Main.ExitCode
 
-import org.apache.daffodil.CLI.Util
+import org.apache.daffodil.CLI.Util._
+import org.apache.daffodil.Main.ExitCode
 
 class TestCLIdebugger {
 
-  val DAFFODIL_JAVA_OPTS = Map("DAFFODIL_JAVA_OPTS" -> "-Xms256m -Xmx2048m -Dfile.encoding=UTF-8")
-
   @Test def test_3385_CLI_Debugger_invalidExpressions(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
 
-      shell.expect(contains("(debug)"))
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-      shell.sendLine("eval (/invalid)")
-      shell.expect(contains("error: expression evaluation failed: Schema Definition Error:"))
-      shell.expect(contains("(debug)"))
+      cli.sendLine("eval (/invalid)")
+      cli.expect("error: expression evaluation failed: Schema Definition Error:")
+      cli.expect("(debug)")
 
-      shell.sendLine("eval (func())")
-      shell.expect(contains("error: expression evaluation failed: Schema Definition Error: Unsupported function:"))
-      shell.expect(contains("(debug)"))
+      cli.sendLine("eval (func())")
+      cli.expect("error: expression evaluation failed: Schema Definition Error: Unsupported function:")
+      cli.expect("(debug)")
 
-      shell.sendLine("eval (/invalid!)")
-      shell.expect(contains("error: expression evaluation failed: Schema Definition Error:"))
-      shell.expect(contains("(debug)"))
+      cli.sendLine("eval (/invalid!)")
+      cli.expect("error: expression evaluation failed: Schema Definition Error:")
+      cli.expect("(debug)")
 
-      shell.sendLine("eval (!)")
-      shell.expect(contains("error: expression evaluation failed: Schema Definition Error:"))
-      shell.expect(contains("(debug)"))
+      cli.sendLine("eval (!)")
+      cli.expect("error: expression evaluation failed: Schema Definition Error:")
+      cli.expect("(debug)")
 
-      shell.sendLine("eval (././.\\/)")
-      shell.expect(contains("error: expression evaluation failed: Schema Definition Error:"))
-      shell.expect(contains("(debug)"))
+      cli.sendLine("eval (././.\\/)")
+      cli.expect("error: expression evaluation failed: Schema Definition Error:")
+      cli.expect("(debug)")
 
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_1591_CLI_Debugger_invalidCommandError(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("garbage")
-      shell.expect(contains("error: undefined command: garbage"))
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("garbage")
+      cli.expect("error: undefined command: garbage")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_1335_CLI_Debugger_dataAndWrapLength(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("debug"))
-
-      shell.sendLine("info data")
-      shell.expect(contains("0~,~1~,~2~,~3~,~4~,~5~,~6~"))
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
 
-      //      shell.sendLine("set dataLength 5")
-      //      shell.sendLine("info data")
-      //      shell.expect(contains("0,1,2"))
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("debug")
 
-      shell.sendLine("set dataLength -938")
-      shell.sendLine("info data")
-      shell.expect(contains("0~,~1~,~2~,~3~,~4~,~5~,~6~"))
+      cli.sendLine("info data")
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
 
-      //      shell.sendLine("set wrapLength 2")
-      //      shell.sendLine("info data")
-      //      shell.expect(contains("0,\n    1,\n    2,\n    3,\n    4,\n    5,\n    6\n"))
+      cli.sendLine("set dataLength -938")
+      cli.sendLine("info data")
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
 
-      shell.sendLine("continue")
+      cli.sendLine("continue")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_982_CLI_Debugger_simpleDebugger(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("continue")
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("continue")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_1326_CLI_Debugger_displaysTesting(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
+      cli.sendLine("display eval (.)")
+      cli.sendLine("step")
+      cli.expect("matrix")
 
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      cli.sendLine("info displays")
+      cli.expect("1: eval (.)")
 
-      shell.sendLine("display eval (.)")
-      shell.sendLine("step")
-      shell.expect(contains("matrix"))
+      cli.sendLine("disable display 1")
+      cli.sendLine("info displays")
+      cli.expect("1*: eval (.)")
+      cli.sendLine("step")
+      cli.sendLine("enable display 1")
 
-      shell.sendLine("info displays")
-      shell.expect(contains("1: eval (.)"))
+      cli.sendLine("step")
+      cli.expect("</tns:cell>")
 
-      shell.sendLine("disable display 1")
-      shell.sendLine("info displays")
-      shell.expect(contains("1*: eval (.)"))
-      shell.sendLine("step")
-      shell.sendLine("enable display 1")
+      cli.sendLine("delete display 1")
+      cli.sendLine("step")
 
-      shell.sendLine("step")
-      shell.expect(contains("</tns:cell>"))
+      cli.sendLine("enable display 1")
+      cli.expect("error: 1 is not a valid display id")
 
-      shell.sendLine("delete display 1")
-      shell.sendLine("step")
+      cli.sendLine("continue")
+      cli.expect("matrix")
 
-      shell.sendLine("enable display 1")
-      shell.expect(contains("error: 1 is not a valid display id"))
-
-      shell.sendLine("continue")
-      shell.expect(contains("matrix"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_1339_CLI_Debugger_removeHidden(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input6.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r e %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-
-      shell.expect(contains("(debug)"))
-      shell.sendLine("set removeHidden false")
-      shell.sendLine("display info infoset")
-      shell.sendLine("step")
-      shell.sendLine("step")
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input6.txt")
+
+    runCLI(args"-d parse -s $schema -r e $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("set removeHidden false")
+      cli.sendLine("display info infoset")
+      cli.sendLine("step")
+      cli.sendLine("step")
       // intentionally look for a newline to make sure normally hidden elements
       // are output with a trailing newline when the debugger displays them
-      shell.expect(contains("<sneaky></sneaky>\n"))
-      shell.sendLine("break g")
-      shell.sendLine("continue")
-      shell.expect(contains("<sneaky>5</sneaky>\n"))
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+      cli.expect("<sneaky></sneaky>\n")
+      cli.sendLine("break g")
+      cli.sendLine("continue")
+      cli.expect("<sneaky>5</sneaky>\n")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_3268_CLI_Debugger_removeHidden2(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input6.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r e %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-
-      shell.expect(contains("(debug)"))
-      shell.sendLine("set removeHidden false")
-      shell.sendLine("display info infoset")
-      shell.sendLine("break g")
-      shell.sendLine("continue")
-      shell.expect(contains("<sneaky>5</sneaky>"))
-      shell.sendLine("continue")
-      val result = shell.expect(contains("</ex:e>")).getBefore();
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input6.txt")
+
+    runCLI(args"-d parse -s $schema -r e $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("set removeHidden false")
+      cli.sendLine("display info infoset")
+      cli.sendLine("break g")
+      cli.sendLine("continue")
+      cli.expect("<sneaky>5</sneaky>")
+      cli.sendLine("continue")
+      val result = cli.expect("</ex:e>").getBefore();
       assert(!result.contains("sneaky"))
 
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_1331_CLI_Debugger_breakpointTesting4(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input3.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input3.txt")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      cli.sendLine("break cell")
+      cli.sendLine("break cell")
 
-      shell.sendLine("break cell")
-      shell.sendLine("break cell")
+      cli.sendLine("condition 1 dfdl:occursIndex() mod 2 eq 1")
+      cli.sendLine("condition 2 dfdl:occursIndex() mod 2 eq 0")
 
-      shell.sendLine("condition 1 dfdl:occursIndex() mod 2 eq 1")
-      shell.sendLine("condition 2 dfdl:occursIndex() mod 2 eq 0")
+      cli.sendLine("info breakpoints")
+      cli.expect("2: cell   { dfdl:occursIndex() mod 2 eq 0 }")
 
-      shell.sendLine("info breakpoints")
-      shell.expect(contains("2: cell   { dfdl:occursIndex() mod 2 eq 0 }"))
+      cli.sendLine("display info occursIndex")
 
-      shell.sendLine("display info occursIndex")
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 1")
 
-      shell.sendLine("continue")
-      shell.expect(contains("occursIndex: 1"))
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 2")
 
-      shell.sendLine("continue")
-      shell.expect(contains("occursIndex: 2"))
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 3")
 
-      shell.sendLine("continue")
-      shell.expect(contains("occursIndex: 3"))
+      cli.sendLine("disable breakpoint 2")
 
-      shell.sendLine("disable breakpoint 2")
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 5")
 
-      shell.sendLine("continue")
-      shell.expect(contains("occursIndex: 5"))
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 7")
 
-      shell.sendLine("continue")
-      shell.expect(contains("occursIndex: 7"))
+      cli.sendLine("enable breakpoint 2")
 
-      shell.sendLine("enable breakpoint 2")
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 8")
 
-      shell.sendLine("continue")
-      shell.expect(contains("occursIndex: 8"))
+      cli.sendLine("disable breakpoint 1")
+      cli.sendLine("disable breakpoint 2")
 
-      shell.sendLine("disable breakpoint 1")
-      shell.sendLine("disable breakpoint 2")
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
 
-      shell.sendLine("continue")
-      shell.expect(contains("<tns:cell>3</tns:cell>"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_1463_CLI_Debugger_breakOnValueOfElement(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input3.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-
-      shell.sendLine("set breakOnlyOnCreation false")
-      shell.expect(contains("(debug)"))
-
-      shell.sendLine("display info infoset")
-      shell.expect(contains("(debug)"))
-
-      shell.sendLine("break cell")
-      shell.expect(contains("1: cell"))
-      shell.sendLine("condition 1 xsd:string(.) eq '3'")
-      shell.expect(contains("1: cell   { xsd:string(.) eq '3' }"))
-
-      shell.sendLine("info breakpoints")
-      shell.expect(allOf(contains("breakpoints:"), contains("1: cell   { xsd:string(.) eq '3' }")))
-
-      shell.sendLine("continue")
-      shell.expect(contains("<tns:cell>3</tns:cell>"))
-      shell.expect(contains("</tns:row>"))
-      shell.expect(contains("</tns:matrix>"))
-      shell.sendLine("continue")
-      shell.expect(contains("<tns:cell>3</tns:cell>"))
-      shell.expect(contains("</tns:row>"))
-      shell.expect(contains("</tns:matrix>"))
-      shell.sendLine("continue")
-      shell.expect(contains("<tns:cell>3</tns:cell>"))
-      shell.expect(contains("</tns:row>"))
-      shell.expect(contains("</tns:matrix>"))
-
-      shell.sendLine("continue")
-      shell.expect(times(1, contains("<tns:cell>3</tns:cell>")))
-      shell.expect(contains("<tns:cell>3</tns:cell>"))
-      shell.expect(contains("</tns:row>"))
-      shell.expect(contains("</tns:matrix>"))
-      shell.sendLine("continue")
-      shell.expect(times(1, contains("<tns:cell>3</tns:cell>")))
-      shell.expect(contains("<tns:cell>3</tns:cell>"))
-      shell.expect(contains("</tns:row>"))
-      shell.expect(contains("</tns:matrix>"))
-
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input3.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("set breakOnlyOnCreation false")
+      cli.expect("(debug)")
+
+      cli.sendLine("display info infoset")
+      cli.expect("(debug)")
+
+      cli.sendLine("break cell")
+      cli.expect("1: cell")
+      cli.sendLine("condition 1 xsd:string(.) eq '3'")
+      cli.expect("1: cell   { xsd:string(.) eq '3' }")
+
+      cli.sendLine("info breakpoints")
+      cli.expect("breakpoints:")
+      cli.expect("1: cell   { xsd:string(.) eq '3' }")
+
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:row>")
+      cli.expect("</tns:matrix>")
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:row>")
+      cli.expect("</tns:matrix>")
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:row>")
+      cli.expect("</tns:matrix>")
+
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:row>")
+      cli.expect("</tns:matrix>")
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:row>")
+      cli.expect("</tns:matrix>")
+
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_1338_CLI_Debugger_pointsOfUncertaintyInfo(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input5.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input5.txt")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    runCLI(args"-d parse -s $schema -r Item2 $input") { cli =>
+      cli.expect("(debug)")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r Item2 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      cli.sendLine("display info pointsOfUncertainty")
 
-      shell.sendLine("display info pointsOfUncertainty")
+      cli.sendLine("step")
+      cli.expect("pointsOfUncertainty:")
+      cli.expect("(none)")
 
-      shell.sendLine("step")
-      shell.expect(contains("pointsOfUncertainty:"))
-      shell.expect(contains("(none)"))
+      cli.sendLine("step")
+      cli.expect("pointsOfUncertainty:")
+      cli.expect("bitPos: 0, context: choice[1]")
 
-      shell.sendLine("step")
-      shell.expect(contains("pointsOfUncertainty:"))
-      shell.expect(contains("bitPos: 0, context: choice[1]"))
+      cli.sendLine("step")
+      cli.expect("pointsOfUncertainty:")
+      cli.expect("(none)")
 
-      shell.sendLine("step")
-      shell.expect(contains("pointsOfUncertainty:"))
-      shell.expect(contains("(none)"))
-
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_1328_CLI_Debugger_breakpointTesting(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-      shell.sendLine("display info infoset")
-      shell.sendLine("break cell")
+      cli.sendLine("display info infoset")
+      cli.sendLine("break cell")
 
-      shell.sendLine("continue")
-      shell.expect(contains("</tns:cell>"))
+      cli.sendLine("continue")
+      cli.expect("</tns:cell>")
 
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(contains("<tns:cell>0</tns:cell>"))
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("<tns:cell>0</tns:cell>")
 
-      shell.sendLine("continue")
-      shell.expect(contains("</tns:cell>"))
+      cli.sendLine("continue")
+      cli.expect("</tns:cell>")
 
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(contains("<tns:cell>1</tns:cell>"))
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("<tns:cell>1</tns:cell>")
 
-      shell.sendLine("delete breakpoint 1")
-      shell.sendLine("continue")
+      cli.sendLine("delete breakpoint 1")
+      cli.sendLine("continue")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_1329_CLI_Debugger_breakpointTesting2(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-      shell.sendLine("display info infoset")
-      shell.sendLine("break cell")
-      shell.sendLine("condition 1 dfdl:occursIndex() eq 3")
+      cli.sendLine("display info infoset")
+      cli.sendLine("break cell")
+      cli.sendLine("condition 1 dfdl:occursIndex() eq 3")
 
-      shell.sendLine("info breakpoints")
-      shell.expect(contains("1: cell   { dfdl:occursIndex() eq 3 }"))
+      cli.sendLine("info breakpoints")
+      cli.expect("1: cell   { dfdl:occursIndex() eq 3 }")
 
-      shell.sendLine("continue")
-      shell.expect(contains("</tns:cell>"))
+      cli.sendLine("continue")
+      cli.expect("</tns:cell>")
 
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(contains("<tns:cell>2</tns:cell>")) // lacks tns: prefix because debugger explicitly strips them.
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("<tns:cell>2</tns:cell>")
 
-      shell.sendLine("continue")
-      shell.expect(contains("<tns:cell>6</tns:cell>")) // has tns prefix because this is the final infoset, not the debugger printing this.
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>6</tns:cell>")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_CLI_Debugger_SDE_message(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      cli.sendLine("display info infoset")
+      cli.sendLine("break cell")
+      cli.sendLine("condition 1 fn:count(../cell) eq 3") // ../cell is wrong. Needs to be ../tns:cell
 
-      shell.sendLine("display info infoset")
-      shell.sendLine("break cell")
-      shell.sendLine("condition 1 fn:count(../cell) eq 3") // ../cell is wrong. Needs to be ../tns:cell
+      cli.sendLine("continue")
+      cli.expect("Schema Definition Error")
+      cli.expect("{}cell")
+      cli.expect("tns:cell")
 
-      shell.sendLine("continue")
-      shell.expect(allOf(contains("Schema Definition Error"), contains("{}cell"), contains("tns:cell")))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_1330_CLI_Debugger_breakpointTesting3(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      cli.sendLine("display info occursIndex")
+      cli.expect("(debug)")
+      cli.sendLine("break cell")
+      cli.expect("(debug)")
+      cli.sendLine("info breakpoints")
+      cli.expect("1: cell")
 
-      shell.sendLine("display info occursIndex")
-      shell.expect(contains("(debug)"))
-      shell.sendLine("break cell")
-      shell.expect(contains("(debug)"))
-      shell.sendLine("info breakpoints")
-      shell.expect(contains("1: cell"))
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 1")
 
-      shell.sendLine("continue")
-      shell.expect(contains("occursIndex: 1"))
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 2")
 
-      shell.sendLine("continue")
-      shell.expect(contains("occursIndex: 2"))
+      cli.sendLine("disable breakpoint 1")
+      cli.sendLine("info breakpoints")
+      cli.expect("1*: cell")
 
-      shell.sendLine("disable breakpoint 1")
-      shell.sendLine("info breakpoints")
-      shell.expect(contains("1*: cell"))
+      cli.sendLine("info data")
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
 
-      shell.sendLine("info data")
-      // shell.expect(contains("(2 to 2)"))
-      shell.expect(contains("0~,~1~,~2~,~3~,~4~,~5~,~6~"))
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>6</tns:cell>")
 
-      shell.sendLine("continue")
-      shell.expect(contains("<tns:cell>6</tns:cell>"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_1333_CLI_Debugger_settingInfosetLines(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input3.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-
-      shell.sendLine("display info infoset")
-      shell.sendLine("set infosetLines 1")
-
-      shell.sendLine("break cell")
-      shell.sendLine("continue")
-      shell.expect(contains("..."))
-      shell.expect(contains("</tns:matrix>"))
-
-      shell.sendLine("set infosetLines 4")
-      shell.sendLine("continue")
-      shell.expect(contains("..."))
-      shell.expect(contains("<tns:cell>3</tns:cell>"))
-      shell.expect(contains("</tns:matrix>"))
-
-      shell.sendLine("set infosetLines 10")
-      shell.sendLine("continue")
-      shell.expect(contains("<tns:matrix"))
-
-      shell.sendLine("set infosetLines -900")
-      shell.sendLine("continue")
-      shell.expect(contains("<tns:matrix"))
-      shell.expect(contains("</tns:matrix>"))
-
-      shell.sendLine("disable breakpoint 1")
-      shell.sendLine("continue")
-
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input3.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info infoset")
+      cli.sendLine("set infosetLines 1")
+
+      cli.sendLine("break cell")
+      cli.sendLine("continue")
+      cli.expect("...")
+      cli.expect("</tns:matrix>")
+
+      cli.sendLine("set infosetLines 4")
+      cli.sendLine("continue")
+      cli.expect("...")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:matrix>")
+
+      cli.sendLine("set infosetLines 10")
+      cli.sendLine("continue")
+      cli.expect("<tns:matrix")
+
+      cli.sendLine("set infosetLines -900")
+      cli.sendLine("continue")
+      cli.expect("<tns:matrix")
+      cli.expect("</tns:matrix>")
+
+      cli.sendLine("disable breakpoint 1")
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
   }
 
   @Test def test_1334_CLI_Debugger_infoBitPosition(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      cli.sendLine("display info bitPosition")
+      cli.sendLine("display info data")
+      cli.sendLine("break cell")
 
-      shell.sendLine("display info bitPosition")
-      shell.sendLine("display info data")
-      shell.sendLine("break cell")
+      cli.sendLine("continue")
+      cli.expect("bitPosition: 0")
 
-      shell.sendLine("continue")
-      shell.expect(contains("bitPosition: 0"))
+      cli.sendLine("continue")
+      cli.expect("bitPosition: 16")
 
-      shell.sendLine("continue")
-      shell.expect(contains("bitPosition: 16"))
+      cli.sendLine("continue")
+      cli.expect("bitPosition: 32")
 
-      shell.sendLine("continue")
-      shell.expect(contains("bitPosition: 32"))
+      cli.sendLine("continue")
 
-      shell.sendLine("continue")
-
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_1337_CLI_Debugger_childIndex(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input4.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input4.txt")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-      shell.sendLine("break cell")
-      shell.sendLine("display info childIndex")
-      shell.sendLine("display info infoset")
+      cli.sendLine("break cell")
+      cli.sendLine("display info childIndex")
+      cli.sendLine("display info infoset")
 
-      shell.sendLine("continue")
-      shell.expect(contains("childIndex: 1"))
+      cli.sendLine("continue")
+      cli.expect("childIndex: 1")
 
-      shell.sendLine("continue")
-      shell.expect(contains("childIndex: 2"))
+      cli.sendLine("continue")
+      cli.expect("childIndex: 2")
 
-      shell.sendLine("continue")
-      shell.expect(contains("childIndex: 4"))
+      cli.sendLine("continue")
+      cli.expect("childIndex: 4")
 
-      shell.sendLine("disable breakpoint 1")
-      shell.sendLine("continue")
+      cli.sendLine("disable breakpoint 1")
+      cli.sendLine("continue")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_1340_CLI_Debugger_infoPath(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-    val output1 = Util.getExpectedString("output1.txt")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      cli.sendLine("break cell")
+      cli.sendLine("display info path")
 
-      shell.sendLine("break cell")
-      shell.sendLine("display info path")
+      cli.sendLine("continue")
+      cli.expect("matrixType::sequence[1]::row::LocalComplexTypeDef::sequence[1]::cell")
 
-      shell.sendLine("continue")
-      shell.expect(contains("matrixType::sequence[1]::row::LocalComplexTypeDef::sequence[1]::cell"))
+      cli.sendLine("delete breakpoint 1")
+      cli.expect("debug")
+      cli.sendLine("continue")
 
-      shell.sendLine("delete breakpoint 1")
-      shell.expect(contains("debug"))
-      shell.sendLine("continue")
-      shell.expect(contains(output1))
+      cli.expect("""<tns:matrix xmlns:tns="http://www.example.org/example1/">""")
+      cli.expect("<tns:cell>2</tns:cell>")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_1382_CLI_Debugger_dataAndWrapLength2(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-
-      shell.sendLine("break cell")
-      shell.sendLine("continue")
-      shell.sendLine("info data")
-      shell.expect(contains("0~,~1~,~2~,~3~,~4~,~5~,~6~"))
-
-      //      shell.sendLine("set dataLength 2")
-      //      shell.sendLine("info data")
-      //      shell.expect(contains("0,"))
-
-      shell.sendLine("set dataLength -938")
-      shell.sendLine("info data")
-      shell.expect(contains("0~,~1~,~2~,~3~,~4~,~5~,~6~"))
-
-      //      shell.sendLine("set wrapLength 2")
-      //      shell.sendLine("info data")
-      //      shell.expect(contains("    0,"))
-      //      shell.expect(contains("    1,"))
-      //      shell.expect(contains("    2,"))
-      //      shell.expect(contains("    3,"))
-      //      shell.expect(contains("    4,"))
-      //      shell.expect(contains("    5,"))
-      //      shell.expect(contains("    6"))
-
-      shell.sendLine("disable breakpoint 1")
-      shell.sendLine("continue")
-
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("break cell")
+      cli.sendLine("continue")
+      cli.sendLine("info data")
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
+
+      cli.sendLine("set dataLength -938")
+      cli.sendLine("info data")
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
+
+      cli.sendLine("disable breakpoint 1")
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
   }
 
   @Test def test_1863_CLI_Debugger_groupIndex01(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_03.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input9.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -r list -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-
-      shell.sendLine("display info groupIndex")
-      shell.sendLine("break price")
-      shell.expect(contains("1: price"))
-      shell.sendLine("break comment")
-      shell.expect(contains("2: comment"))
-
-      shell.sendLine("continue")
-      shell.expect(contains("groupIndex: 2"))
-      shell.sendLine("continue")
-      shell.expect(contains("groupIndex: 4"))
-      shell.sendLine("continue")
-      shell.expect(contains("groupIndex: 2"))
-      shell.sendLine("continue")
-      shell.expect(contains("groupIndex: 4"))
-      shell.sendLine("continue")
-      shell.expect(contains("<ex:price>89.99</ex:price>"))
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_03.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input9.txt")
+
+    runCLI(args"-d parse -r list -s $schema $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info groupIndex")
+      cli.sendLine("break price")
+      cli.expect("1: price")
+      cli.sendLine("break comment")
+      cli.expect("2: comment")
+
+      cli.sendLine("continue")
+      cli.expect("groupIndex: 2")
+      cli.sendLine("continue")
+      cli.expect("groupIndex: 4")
+      cli.sendLine("continue")
+      cli.expect("groupIndex: 2")
+      cli.sendLine("continue")
+      cli.expect("groupIndex: 4")
+      cli.sendLine("continue")
+      cli.expect("<ex:price>89.99</ex:price>")
+    } (ExitCode.Success)
   }
 
   @Test def test_1029_CLI_Debugger_validation1(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_03.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input9.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -r list -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-
-      shell.sendLine("display info dne1")
-      shell.expect(contains("error: undefined info command: dne1"))
-      shell.sendLine("display info bitLimit dne2")
-      shell.expect(contains("error: bitLimit command requires zero arguments"))
-      shell.sendLine("display break")
-      shell.expect(contains("error: undefined command: break"))
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_03.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input9.txt")
+
+    runCLI(args"-d parse -r list -s $schema $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info dne1")
+      cli.expect("error: undefined info command: dne1")
+      cli.sendLine("display info bitLimit dne2")
+      cli.expect("error: bitLimit command requires zero arguments")
+      cli.sendLine("display break")
+      cli.expect("error: undefined command: break")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_3258_CLI_Debugger_infodata(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-
-      shell.sendLine("display info data")
-      shell.sendLine("step")
-      shell.expect(contains("│")) //  (0 to 0)
-      shell.expect(contains("0~,~1~,~2~,~3~,~4~,~5~,~6~"))
-
-      shell.sendLine("break cell")
-      shell.sendLine("condition 1 dfdl:occursIndex() eq 5")
-      shell.sendLine("continue")
-
-      // Gaak. Eclipse default font isn't monospaced. The visible space character is wider than a regular character!
-      shell.expect(contains("""                                  │                                    │"""))
-      shell.expect(contains("""    87654321  0011 2233 4455 6677 8899 aabb ccdd eeff  0~1~2~3~4~5~6~7~8~9~a~b~c~d~e~f~"""))
-      shell.expect(contains("""    00000000: 302c 312c 322c 332c 342c 352c 36         0~,~1~,~2~,~3~,~4~,~5~,~6~      """))
-      shell.sendLine("continue")
-//      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info data")
+      cli.sendLine("step")
+      cli.expect("│") //  (0 to 0)
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
+
+      cli.sendLine("break cell")
+      cli.sendLine("condition 1 dfdl:occursIndex() eq 5")
+      cli.sendLine("continue")
+
+      cli.expect("""                                  │                                    │""")
+      cli.expect("""    87654321  0011 2233 4455 6677 8899 aabb ccdd eeff  0~1~2~3~4~5~6~7~8~9~a~b~c~d~e~f~""")
+      cli.expect("""    00000000: 302c 312c 322c 332c 342c 352c 36         0~,~1~,~2~,~3~,~4~,~5~,~6~      """)
+      cli.sendLine("continue")
+    } (ExitCode.Success)
   }
 
   @Test def test_3264_CLI_Debugger_undefined_command(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      cli.sendLine("display data")
+      cli.expect("error: undefined command: data")
 
-      shell.sendLine("display data")
-      shell.expect(contains("error: undefined command: data"))
+      cli.sendLine("set breakonfailure true")
+      cli.expect("error: undefined command: breakonfailure")
 
-      shell.sendLine("set breakonfailure true")
-      shell.expect(contains("error: undefined command: breakonfailure"))
+      cli.sendLine("continue")
 
-      shell.sendLine("continue")
-
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_CLI_Debugger_delimiterStack(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input2.txt")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      cli.sendLine("break row")
+      cli.expect("(debug)")
 
-      shell.sendLine("break row")
-      shell.expect(contains("(debug)"))
+      cli.sendLine("continue")
+      cli.expect("(debug)")
 
-      shell.sendLine("continue")
-      shell.expect(contains("(debug)"))
+      cli.sendLine("info delimiterStack")
+      cli.expect("local:  %NL; (separator)")
+      cli.expect("(debug)")
 
-      shell.sendLine("info delimiterStack")
-      shell.expect(contains("""local:  %NL; (separator)"""))
-      shell.expect(contains("(debug)"))
+      cli.sendLine("break cell")
+      cli.expect("(debug)")
 
-      shell.sendLine("break cell")
-      shell.expect(contains("(debug)"))
+      cli.sendLine("continue")
+      cli.expect("(debug)")
 
-      shell.sendLine("continue")
-      shell.expect(contains("(debug)"))
+      cli.sendLine("info delimiterStack")
+      cli.expect("remote: %NL; (separator)")
+      cli.expect("local:  , (separator)")
 
-      shell.sendLine("info delimiterStack")
-      shell.expect(contains("""remote: %NL; (separator)"""))
-      shell.expect(contains("""local:  , (separator)"""))
-
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_CLI_Debugger_utf16_encoding(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/utf16schema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/hextest.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/utf16schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/hextest.txt")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r e2 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+    runCLI(args"-d parse -s $schema -r e2 $input") { cli =>
+      cli.expect("(debug)")
 
-      shell.sendLine("info data")
-      shell.expect(contains("\u240A"))
+      cli.sendLine("info data")
+      cli.expect("\u240A")
 
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_1337_CLI_Debugger_info_infoset(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
 
-      shell.sendLine("info infoset")
-      shell.expect(contains("No Infoset"))
+      cli.sendLine("info infoset")
+      cli.expect("No Infoset")
 
-      shell.sendLine("step")
-      shell.sendLine("info infoset")
-      shell.expect(contains("matrix"))
+      cli.sendLine("step")
+      cli.sendLine("info infoset")
+      cli.expect("matrix")
 
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_CLI_Debugger_InfoHidden_1(): Unit = {
-    val schemaFile = Util.daffodilPath(
-      "daffodil-test/src/test/resources/org/apache/daffodil/section14/sequence_groups/SequencesWithHiddenRefs.dfdl.xsd")
-    val inputFile = Util.newTempFile("testInput_", ".tmp", optFileContents = Some("2~3"))
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) {
-      (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile.getAbsolutePath))
-    } else {
-      (schemaFile, inputFile)
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section14/sequence_groups/SequencesWithHiddenRefs.dfdl.xsd")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    withTempFile { input =>
+      Files.write(input, "2~3".getBytes(UTF_8))
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r e5 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      runCLI(args"-d parse -s $schema -r e5 $input") { cli =>
+        cli.expect("(debug)")
 
-      shell.sendLine("break f")
-      shell.sendLine("display info hidden")
+        cli.sendLine("break f")
+        cli.sendLine("display info hidden")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains("<f xmlns=\"\">2</f>"))
+        cli.sendLine("continue")
+        cli.expect("<f xmlns=\"\">2</f>")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
+      } (ExitCode.Success)
     }
   }
 
   @Test def test_CLI_Debugger_InfoHidden_2(): Unit = {
-    val schemaFile = Util.daffodilPath(
-      "daffodil-test/src/test/resources/org/apache/daffodil/section14/sequence_groups/SequencesWithHiddenRefs.dfdl.xsd")
-    val inputFile = Util.newTempFile("testInput_", ".tmp", optFileContents = Some("2~3"))
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) {
-      (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile.getAbsolutePath))
-    } else {
-      (schemaFile, inputFile)
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section14/sequence_groups/SequencesWithHiddenRefs.dfdl.xsd")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    withTempFile { input =>
+      Files.write(input, "2~3".getBytes(UTF_8))
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r e4 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      runCLI(args"-d parse -s $schema -r e4 $input") { cli =>
+        cli.expect("(debug)")
 
-      shell.sendLine("break f")
-      shell.sendLine("display info hidden")
+        cli.sendLine("break f")
+        cli.sendLine("display info hidden")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains("<f xmlns=\"\">3</f>"))
+        cli.sendLine("continue")
+        cli.expect("<f xmlns=\"\">3</f>")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
+      } (ExitCode.Success)
     }
   }
 
   @Test def test_CLI_Debugger_InfoHidden_3(): Unit = {
-    val schemaFile = Util.daffodilPath(
-      "daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoicesInHiddenContexts.dfdl.xsd")
-    val inputFile = Util.newTempFile("testInput_", ".tmp", optFileContents = Some("2,3"))
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) {
-      (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile.getAbsolutePath))
-    } else {
-      (schemaFile, inputFile)
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoicesInHiddenContexts.dfdl.xsd")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    withTempFile { input =>
+      Files.write(input, "2,3".getBytes(UTF_8))
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r e8 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      runCLI(args"-d parse -s $schema -r e8 $input") { cli =>
+        cli.expect("(debug)")
 
-      shell.sendLine("break a")
-      shell.sendLine("break h")
-      shell.sendLine("break g")
-      shell.sendLine("break e")
-      shell.sendLine("break f")
-      shell.sendLine("display info hidden")
+        cli.sendLine("break a")
+        cli.sendLine("break h")
+        cli.sendLine("break g")
+        cli.sendLine("break e")
+        cli.sendLine("break f")
+        cli.sendLine("display info hidden")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains("<a>2</a>"))
-      shell.expect(contains("<g></g>"))
-    } finally {
-      shell.close()
+        cli.sendLine("continue")
+        cli.expect("<a>2</a>")
+        cli.expect("<g></g>")
+      } (ExitCode.Success)
     }
   }
 
   @Test def test_CLI_Debugger_InfoHidden_4(): Unit = {
-    val schemaFile = Util.daffodilPath(
-      "daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoicesInHiddenContexts.dfdl.xsd")
-    val inputFile = Util.newTempFile("testInput_", ".tmp", optFileContents = Some("[6~]9"))
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) {
-      (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile.getAbsolutePath))
-    } else {
-      (schemaFile, inputFile)
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoicesInHiddenContexts.dfdl.xsd")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    withTempFile { input =>
+      Files.write(input, "[6~]9".getBytes(UTF_8))
 
-    try {
-      val cmd = String.format("%s -d parse -s %s -r e9 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      runCLI(args"-d parse -s $schema -r e9 $input") { cli =>
+        cli.expect("(debug)")
 
-      shell.sendLine("break e")
-      shell.sendLine("break f")
-      shell.sendLine("break g")
-      shell.sendLine("break h")
-      shell.sendLine("break i")
-      shell.sendLine("display info path hidden")
+        cli.sendLine("break e")
+        cli.sendLine("break f")
+        cli.sendLine("break g")
+        cli.sendLine("break h")
+        cli.sendLine("break i")
+        cli.sendLine("display info path hidden")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":f"))
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect(":f")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":i"))
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect(":i")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":h"))
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect(":h")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":e"))
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect(":e")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":f"))
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect(":f")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":f"))
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect(":f")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":g"))
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect(":g")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":i"))
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect(":i")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":h"))
-      shell.expect(contains("hidden: false"))
+        cli.sendLine("continue")
+        cli.expect(":h")
+        cli.expect("hidden: false")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":e"))
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect(":e")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains(":f"))
-      shell.expect(contains("hidden: true"))
+        cli.sendLine("continue")
+        cli.expect(":f")
+        cli.expect("hidden: true")
 
-      shell.sendLine("continue")
-      shell.expect(contains("<h></h>"))
-    } finally {
-      shell.close()
+        cli.sendLine("continue")
+        cli.expect("<h></h>")
+      } (ExitCode.Success)
     }
   }
-  /* See DFDL-1264
-  @Test def test_3585_CLI_Debugger_simpleDebugger_unparse() {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input12.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d unparse -s %s -r e1 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("break e1")
-      shell.expect(contains("1: e1"))
-      shell sendLine ("continue")
-      shell.expect(contains("Hello  breakpoint 1: e1"))
-      shell.sendLine("info data")
-      shell.expect(contains(
-        """4865 6c6c 6f                             Hello"""))
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
-  }*/
+
+  @Test def test_3585_CLI_Debugger_simpleDebugger_unparse(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input12.txt")
+
+    runCLI(args"-d unparse -s $schema -r e1 $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("break e1")
+      cli.expect("1: e1")
+      cli.sendLine("continue")
+      cli.expect("Hello  breakpoint 1: e1")
+      cli.sendLine("info data")
+      cli.expect("4865 6c6c 6f                             Hello")
+    } (ExitCode.Failure)
+  }
 
   @Test def test_3585_CLI_Debugger_prefixLength(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/prefixed_length.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/prefix.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("display info infoset")
-      shell.expect(contains("(debug)"))
-      shell.sendLine("display eval .")
-      shell.sendLine("step")
-      shell.expect(contains("<field></field>"))
-      shell.sendLine("step")
-      shell.expect(contains("<field (prefixLength)></field (prefixLength)>"))
-      shell.sendLine("step")
-      shell.expect(contains("<field (prefixLength)>4</field (prefixLength)>"))
-      shell.sendLine("step")
-      shell.expect(contains("<field>abcd</field>"))
-      shell.sendLine("complete")
-      shell.expect(contains("<field>abcd</field>"))
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/prefixed_length.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/prefix.txt")
+
+    runCLI(args"-d parse -s $schema $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info infoset")
+      cli.expect("(debug)")
+      cli.sendLine("display eval .")
+      cli.sendLine("step")
+      cli.expect("<field></field>")
+      cli.sendLine("step")
+      cli.expect("<field (prefixLength)></field (prefixLength)>")
+      cli.sendLine("step")
+      cli.expect("<field (prefixLength)>4</field (prefixLength)>")
+      cli.sendLine("step")
+      cli.expect("<field>abcd</field>")
+      cli.sendLine("complete")
+      cli.expect("<field>abcd</field>")
+    } (ExitCode.Success)
   }
 
   @Test def test_CLI_Debugger_info_variables(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("info variables byteOrder")
-      shell.expect(contains("byteOrder: bigEndian (default)"))
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("info variables byteOrder")
+      cli.expect("byteOrder: bigEndian (default)")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_CLI_Debugger_info_data_text(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("display info data text")
-      shell.expect(contains("(debug)"))
-      shell.sendLine("step")
-      shell.expect(contains("0~,~1~,~2~"))
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info data text")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.expect("0~,~1~,~2~")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_CLI_Debugger_info_data_binary(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("display info data binary")
-      shell.expect(contains("(debug)"))
-      shell.sendLine("step")
-      shell.expect(contains("302c 312c 32"))
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info data binary")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.expect("302c 312c 32")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_CLI_Debugger_info_diff_01(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables_01.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r c %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("display info diff")
-      shell.expect(contains("(debug)"))
-      shell.sendLine("step")
-      shell.expect(contains("(no differences)"))
-      shell.sendLine("step")
-      shell.expect(contains("(no differences)"))
-      shell.sendLine("step")
-      shell.expect(contains("variable: tns:v_with_default: 42 (default) -> 42 (read)"))
-      shell.sendLine("step")
-      shell.expect(contains("variable: tns:v_no_default: (undefined) -> 42 (set)"))
-      shell.sendLine("step")
-      shell.expect(contains("childIndex: 1 -> 2"))
-      shell.sendLine("step")
-      shell.expect(contains("variable: tns:v_no_default: 42 (set) -> 42 (read)"))
-      shell.sendLine("step")
-      shell.expect(contains("<d>42</d>"))
-      shell.expect(contains("<e>42</e>"))
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables_01.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r c $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info diff")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.expect("(no differences)")
+      cli.sendLine("step")
+      cli.expect("(no differences)")
+      cli.sendLine("step")
+      cli.expect("variable: tns:v_with_default: 42 (default) -> 42 (read)")
+      cli.sendLine("step")
+      cli.expect("variable: tns:v_no_default: (undefined) -> 42 (set)")
+      cli.sendLine("step")
+      cli.expect("childIndex: 1 -> 2")
+      cli.sendLine("step")
+      cli.expect("variable: tns:v_no_default: 42 (set) -> 42 (read)")
+      cli.sendLine("step")
+      cli.expect("<d>42</d>")
+      cli.expect("<e>42</e>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_CLI_Debugger_info_diff_02(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("display info diff")
-      shell.expect(contains("(debug)"))
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(contains("bitPosition: 0 -> 8"))
-      shell.expect(contains("foundDelimiter: (no value) -> ,"))
-      shell.expect(contains("foundField: (no value) -> 0"))
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(contains("bitPosition: 8 -> 16"))
-      shell.expect(contains("childIndex: 1 -> 2"))
-      shell.expect(contains("foundDelimiter: , -> (no value)"))
-      shell.expect(contains("foundField: 0 -> (no value)"))
-      shell.expect(contains("groupIndex: 1 -> 2"))
-      shell.expect(contains("occursIndex: 1 -> 2"))
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(contains("bitPosition: 16 -> 24"))
-      shell.expect(contains("foundDelimiter: (no value) -> ,"))
-      shell.expect(contains("foundField: (no value) -> 1"))
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info diff")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("bitPosition: 0 -> 8")
+      cli.expect("foundDelimiter: (no value) -> ,")
+      cli.expect("foundField: (no value) -> 0")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("bitPosition: 8 -> 16")
+      cli.expect("childIndex: 1 -> 2")
+      cli.expect("foundDelimiter: , -> (no value)")
+      cli.expect("foundField: 0 -> (no value)")
+      cli.expect("groupIndex: 1 -> 2")
+      cli.expect("occursIndex: 1 -> 2")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("bitPosition: 16 -> 24")
+      cli.expect("foundDelimiter: (no value) -> ,")
+      cli.expect("foundField: (no value) -> 1")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_CLI_Debugger_info_diff_03(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input6.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d parse -s %s -r e %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("display info diff")
-      shell.expect(contains("(debug)"))
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(contains("hidden: false -> true"))
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(contains("hidden: true -> false"))
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input6.txt")
+
+    runCLI(args"-d parse -s $schema -r e $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info diff")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("hidden: false -> true")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("hidden: true -> false")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_CLI_Debugger_info_diff_04(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt.xml")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d unparse -s %s -r matrix -o %s %s", Util.binPath, testSchemaFile, Util.devNull, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("display info diff")
-      shell.expect(contains("(debug)"))
-      shell.sendLine("set diffExcludes childIndex")
-      shell.expect(contains("(debug)"))
-      shell.sendLine("step")
-      shell.expect(contains("bitPosition: 0 -> 8"))
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(regexp("\\+ Suppressable.* for cell"))
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(regexp("RegionSplit.* for cell"))
-      shell.sendLine("info suspensions")
-      shell.expect(regexp("Suppressable.* for cell"))
-      shell.expect(regexp("RegionSplit.* for cell"))
-      shell.sendLine("quit")
-      Util.expectExitCode(ExitCode.Failure, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt.xml")
+
+    runCLI(args"-d unparse -s $schema -r matrix -o $devNull $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info diff")
+      cli.expect("(debug)")
+      cli.sendLine("set diffExcludes childIndex")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.expect("bitPosition: 0 -> 8")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect(regexp("\\+ Suppressable.* for cell"))
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect(regexp("RegionSplit.* for cell"))
+      cli.sendLine("info suspensions")
+      cli.expect(regexp("Suppressable.* for cell"))
+      cli.expect(regexp("RegionSplit.* for cell"))
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
   }
 
   @Test def test_CLI_Debugger_info_diff_05(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_03.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input9.txt.xml")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
-
-    try {
-      val cmd = String.format("%s -d unparse -r list -s %s -o %s %s", Util.binPath, testSchemaFile, Util.devNull, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
-      shell.sendLine("set diffExcludes doesNotExist1 bitLimit doesNotExist2")
-      shell.expect(contains("unknown or undiffable info commands: doesNotExist1, doesNotExist2"))
-
-      shell.sendLine("display info diff")
-      shell.sendLine("break Item")
-      shell.sendLine("continue")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(regexp("\\+ SuppressableSeparator.* ex:Item"))
-      shell.sendLine("continue")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(regexp("\\+ RegionSplit.* ex:Item"))
-      shell.sendLine("step")
-      shell.sendLine("step")
-      shell.expect(regexp("\\- RegionSplit.* ex:Item"))
-      shell.sendLine("info suspensions")
-      shell.expect(regexp("SuppressableSeparator.* ex:Item"))
-      shell.sendLine("step")
-      shell.sendLine("step")
-
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_03.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input9.txt.xml")
+
+    runCLI(args"-d unparse -r list -s $schema -o $devNull $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("set diffExcludes doesNotExist1 bitLimit doesNotExist2")
+      cli.expect("unknown or undiffable info commands: doesNotExist1, doesNotExist2")
+
+      cli.sendLine("display info diff")
+      cli.sendLine("break Item")
+      cli.sendLine("continue")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect(regexp("\\+ SuppressableSeparator.* ex:Item"))
+      cli.sendLine("continue")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect(regexp("\\+ RegionSplit.* ex:Item"))
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect(regexp("\\- RegionSplit.* ex:Item"))
+      cli.sendLine("info suspensions")
+      cli.expect(regexp("SuppressableSeparator.* ex:Item"))
+      cli.sendLine("step")
+      cli.sendLine("step")
+
+    } (ExitCode.Success)
   }
 
   @Test def test_CLI_Debugger_parse_unparser_not_available(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input3.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input3.txt")
 
-    try {
-      val cmd = String.format("%s -d parse -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+    runCLI(args"-d parse -s $schema $input") { cli =>
+      cli.expect("(debug)")
 
-      shell.sendLine("info parser")
-      shell.expect(contains("parser: <Element name='matrix'><DelimiterStackParser>...</DelimiterStackParser></Element>"))
+      cli.sendLine("info parser")
+      cli.expect("parser: <Element name='matrix'><DelimiterStackParser>...</DelimiterStackParser></Element>")
 
-      shell.sendLine("info unparser")
-      shell.expect(contains("unparser: not available"))
+      cli.sendLine("info unparser")
+      cli.expect("unparser: not available")
 
-      shell.sendLine("continue")
+      cli.sendLine("continue")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
   @Test def test_CLI_Debugger_unparse_parser_not_available(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt.xml")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt.xml")
 
-    val shell = if (Util.isWindows) Util.start("", envp = DAFFODIL_JAVA_OPTS) else Util.start("")
+    runCLI(args"-d unparse -s $schema $input") { cli =>
+      cli.expect("(debug)")
 
-    try {
-      val cmd = String.format("%s -d unparse -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("(debug)"))
+      cli.sendLine("info unparser")
+      cli.expect("unparser: <ConvertTextNumberUnparser/>")
 
-      shell.sendLine("info unparser")
-      shell.expect(contains("unparser: <ConvertTextNumberUnparser/>"))
+      cli.sendLine("info parser")
+      cli.expect("parser: not available")
 
-      shell.sendLine("info parser")
-      shell.expect(contains("parser: not available"))
+      cli.sendLine("continue")
 
-      shell.sendLine("continue")
-
-      Util.expectExitCode(ExitCode.Success, shell)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.Success)
   }
 
 }
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/executing/TestCLIexecuting.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/executing/TestCLIexecuting.scala
deleted file mode 100644
index 0fd008f48..000000000
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/executing/TestCLIexecuting.scala
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.daffodil.executing
-
-import org.junit.Assert._
-import org.junit.Test
-import org.apache.daffodil.CLI.Util
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.matches
-import net.sf.expectit.ExpectIOException
-import org.apache.daffodil.Main.ExitCode
-
-class TestCLIexecuting {
-
-  val output3 = Util.getExpectedString("output3.txt")
-  val output13 = Util.getExpectedString("output13.txt", true)
-  val output14 = Util.getExpectedString("output14.txt", true)
-  val output15 = Util.getExpectedString("output15.txt", true)
-  val output16 = Util.getExpectedString("output16.txt", true)
-
-  @Test def test_995_CLI_Executing_Listing_negativeTest01(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml")
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s test %s escape_entry1 escape_entry2-11 escape_entry1-5 escape_entry4_3", Util.binPath, testTdmlFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output3))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-    } finally {
-      shell.close()
-    }
-  }
-
-  @Test def test_1001_CLI_Executing_Listing_execRegex01(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml")
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s test --regex %s \"escape_entry4_\\d\"", Util.binPath, testTdmlFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output13))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-    } finally {
-      shell.close()
-    }
-  }
-
-  @Test def test_1000_CLI_Executing_Listing_listRegex02(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml")
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-
-    val shell = Util.start("", timeout = 5)
-    try {
-      val cmd = String.format("%s test -l --regex %s \"escape_entryb-\\d+\"", Util.binPath, testTdmlFile)
-      shell.sendLine(cmd)
-      shell.expect(matches(""))
-    } catch {
-      case ex: ExpectIOException => {
-        fail("Output was found when none was expected.")
-      }
-    } finally {
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.close()
-    }
-  }
-
-  @Test def test_999_CLI_Executing_Listing_listRegex01(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml")
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s test -l --regex %s \"escape_entry4_\\d+\"", Util.binPath, testTdmlFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output14))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-    } finally {
-      shell.close()
-    }
-  }
-
-  //
-  // Test removed to scala-debug as order of execution of the individual tests is not deterministic.
-  // You'd like them to go in the order they appear in the file, but that's not the case
-  // necessarily: http://stackoverflow.com/questions/3693626/how-to-run-test-methods-in-specific-order-in-junit4
-  // JIRA DFDL-1240
-  //
-  //  @Test def test_994_CLI_Executing_Listing_execAll() {
-  //    val tdmlFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml")
-  //    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-  //
-  //    val shell = Util.start("")
-  //
-  //    try {
-  //      val cmd = String.format("%s test %s", Util.binPath, testTdmlFile)
-  //      shell.sendLine(cmd)
-  //      shell.expect(contains(output15))
-  //      shell.sendLine("exit")
-  //    } finally {
-  //      shell.close()
-  //    }
-  //  }
-
-  @Test def test_993_CLI_Executing_Listing_listAll(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/Entities.tdml")
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-
-    val shell = Util.start("")
-
-    try {
-      shell.sendLine(String.format("%s test -l %s", Util.binPath, testTdmlFile))
-      shell.expect(contains(output16))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-    } finally {
-      shell.close()
-    }
-  }
-
-  @Test def test_992_CLI_Executing_Listing_singleTestList(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/Entities.tdml")
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s test -l %s byte_entities_6_08", Util.binPath, testTdmlFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("byte_entities_6_08"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-    } finally {
-      shell.close()
-    }
-  }
-
-  @Test def test_990_CLI_Executing_Listing_singleTest(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/Entities.tdml")
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s test %s byte_entities_6_08", Util.binPath, testTdmlFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("[Pass] byte_entities_6_08"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-    } finally {
-      shell.close()
-    }
-  }
-
-  @Test def test_CLI_catch_TestNotCompatible(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/testNonCompatibleImplementation.tdml")
-
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s test %s testNotCompatibleImplementation1", Util.binPath, testTdmlFile)
-      println(cmd)
-      shell.sendLine(cmd)
-      shell.expect(contains("[Skipped] testNotCompatibleImplementation1 (not compatible"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-    } finally {
-      shell.close()
-    }
-  }
-
-  @Test def test_CLI_catch_TestBadArguments(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/testNonCompatibleImplementation.tdml")
-
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s test -I notDaffodilC %s", Util.binPath, testTdmlFile)
-      println(cmd)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("[error] Bad arguments for option 'implementation'"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine("exit")
-    } finally {
-      shell.close()
-    }
-  }
-
-  @Test def test_CLI_Executing_implementation(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/testNonCompatibleImplementation.tdml")
-
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s test -I daffodilC %s testDaffodilCImplementation1", Util.binPath, testTdmlFile)
-      println(cmd)
-      shell.sendLine(cmd)
-      shell.expect(contains("[Pass] testDaffodilCImplementation1"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-    } finally {
-      shell.close()
-    }
-  }
-}
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/generating/TestCLIGenerateC.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/generating/TestCLIGenerateC.scala
index 22ed84be0..358b8038f 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/generating/TestCLIGenerateC.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/generating/TestCLIGenerateC.scala
@@ -17,12 +17,13 @@
 
 package org.apache.daffodil.generating
 
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.eof
-import org.apache.daffodil.CLI.Util
-import org.apache.daffodil.Main.ExitCode
-import org.junit.After
+import java.nio.file.Files.exists
+
 import org.junit.Test
+import org.junit.Assert.assertTrue
+
+import org.apache.daffodil.CLI.Util._
+import org.apache.daffodil.Main.ExitCode
 
 /**
  * Checks that we can run the "daffodil generate c" subcommand with
@@ -30,190 +31,106 @@ import org.junit.Test
  */
 class TestCLIGenerateC {
 
-  val daffodil: String = Util.binPath
-  lazy val schemaFile: String = if (Util.isWindows) Util.cmdConvert(sf) else sf
-  val sf: String = Util.daffodilPath("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
-  // Ensure all tests remove tempDir after creating it
-  val tempDir: os.Path = os.temp.dir()
-
-  @After def after(): Unit = {
-    os.remove.all(tempDir)
-  }
-
   @Test def test_CLI_Generate_schema(): Unit = {
-    val generateCmd = s"$daffodil generate c -s $schemaFile $tempDir"
-    val exitCmd = "exit"
-
-    val shell = Util.start("")
-    try {
-      shell.sendLine(generateCmd)
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine(exitCmd)
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
 
-    assert(os.exists(tempDir/"c"/"libruntime"/"generated_code.c"))
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema $tempDir") { cli =>
+      } (ExitCode.Success)
+      assertTrue(exists(tempDir.resolve("c/libruntime/generated_code.c")))
+    }
   }
 
   @Test def test_CLI_Generate_noC_error(): Unit = {
-    val generateCmd = s"$daffodil generate -s $schemaFile $tempDir"
-    val exitCmd = "exit"
-
-    val shell = Util.start("")
-    try {
-      shell.sendLine(generateCmd)
-      shell.expectIn(1, contains("Unknown option 's'"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine(exitCmd)
-      shell.expectIn(1, eof())
-    } finally {
-      shell.close()
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate -s $schema $tempDir") { cli =>
+        cli.expectErr("Unknown option 's'")
+      } (ExitCode.Usage)
     }
   }
 
   @Test def test_CLI_Generate_otherThanC_error(): Unit = {
-    val generateCmd = s"$daffodil generate vhld -s $schemaFile $tempDir"
-    val exitCmd = "exit"
-
-    val shell = Util.start("")
-    try {
-      shell.sendLine(generateCmd)
-      shell.expectIn(1, contains("Unknown option 's'"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine(exitCmd)
-      shell.expectIn(1, eof())
-    } finally {
-      shell.close()
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate vhld -s $schema $tempDir") { cli =>
+        cli.expectErr("Unknown option 's'")
+      } (ExitCode.Usage)
     }
   }
 
   @Test def test_CLI_Generate_noSchema_error(): Unit = {
-    val generateCmd = s"$daffodil generate c $tempDir"
-    val exitCmd = "exit"
-
-    val shell = Util.start("")
-    try {
-      shell.sendLine(generateCmd)
-      shell.expectIn(1, contains("Required option 'schema' not found"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine(exitCmd)
-      shell.expectIn(1, eof())
-    } finally {
-      shell.close()
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate c $tempDir") { cli =>
+        cli.expectErr("Required option 'schema' not found")
+      } (ExitCode.Usage)
     }
   }
 
   @Test def test_CLI_Generate_twoSchema_error(): Unit = {
-    val generateCmd = s"$daffodil generate c -s $schemaFile -s $schemaFile $tempDir"
-    val exitCmd = "exit"
-
-    val shell = Util.start("")
-    try {
-      shell.sendLine(generateCmd)
-      shell.expectIn(1, contains("you should provide exactly one argument"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine(exitCmd)
-      shell.expectIn(1, eof())
-    } finally {
-      shell.close()
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema -s $schema $tempDir") { cli =>
+        cli.expectErr("you should provide exactly one argument")
+      } (ExitCode.Usage)
     }
   }
 
   @Test def test_CLI_Generate_verbose(): Unit = {
-    val generateCmd = s"$daffodil -v generate c -s $schemaFile $tempDir"
-    val exitCmd = "exit"
-
-    val shell = Util.start("")
-    try {
-      shell.sendLine(generateCmd)
-      shell.expectIn(1, contains("[info] Time (compiling)"))
-      shell.expectIn(1, contains("[info] Time (generating)"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine(exitCmd)
-      shell.expectIn(1, eof())
-    } finally {
-      shell.close()
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"-v generate c -s $schema $tempDir") { cli =>
+        cli.expectErr("[info] Time (compiling)")
+        cli.expectErr("[info] Time (generating)")
+      } (ExitCode.Success)
+      assertTrue(exists(tempDir.resolve("c/libruntime/generated_code.c")))
     }
-
-    assert(os.exists(tempDir/"c"/"libruntime"/"generated_code.c"))
   }
 
   @Test def test_CLI_Generate_root(): Unit = {
-    val generateCmd = s"$daffodil generate c -s $schemaFile -r {http://example.com}ex_nums $tempDir"
-    val exitCmd = "exit"
-
-    val shell = Util.start("")
-    try {
-      shell.sendLine(generateCmd)
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine(exitCmd)
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
 
-    assert(os.exists(tempDir/"c"/"libruntime"/"generated_code.c"))
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema -r {http://example.com}ex_nums $tempDir") { cli =>
+      } (ExitCode.Success)
+      assertTrue(exists(tempDir.resolve("c/libruntime/generated_code.c")))
+    }
   }
 
   @Test def test_CLI_Generate_root_error(): Unit = {
-    val generateCmd = s"$daffodil generate c -s $schemaFile -r {ex}ex_nums $tempDir"
-    val exitCmd = "exit"
-
-    val shell = Util.start("")
-    try {
-      shell.sendLine(generateCmd)
-      shell.expectIn(1, contains("Schema Definition Error"))
-      shell.expectIn(1, contains("No global element found for {ex}ex_nums"))
-
-      Util.expectExitCode(ExitCode.GenerateCodeError, shell)
-      shell.sendLine(exitCmd)
-      shell.expectIn(1, eof())
-    } finally {
-      shell.close()
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+    
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema -r {ex}ex_nums $tempDir") { cli =>
+        cli.expectErr("Schema Definition Error")
+        cli.expectErr("No global element found for {ex}ex_nums")
+      } (ExitCode.GenerateCodeError)
     }
   }
 
   @Test def test_CLI_Generate_namespaceNoRoot_error(): Unit = {
-    val generateCmd = s"$daffodil generate c -s $schemaFile -r {http://example.com} $tempDir"
-    val exitCmd = "exit"
-
-    val shell = Util.start("")
-    try {
-      shell.sendLine(generateCmd)
-      shell.expectIn(1, contains("Invalid syntax for extended QName"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine(exitCmd)
-      shell.expectIn(1, eof())
-    } finally {
-      shell.close()
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema -r {http://example.com} $tempDir") { cli =>
+      cli.expectErr("Invalid syntax for extended QName")
+      } (ExitCode.Usage)
     }
   }
 
   @Test def test_CLI_Generate_tunable(): Unit = {
-    val generateCmd = s"$daffodil generate c -s $schemaFile -T parseUnparsePolicy=parseOnly $tempDir"
-    val exitCmd = "exit"
-
-    val shell = Util.start("")
-    try {
-      shell.sendLine(generateCmd)
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine(exitCmd)
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
 
-    assert(os.exists(tempDir/"c"/"libruntime"/"generated_code.c"))
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema -T parseUnparsePolicy=parseOnly $tempDir") { cli =>
+      } (ExitCode.Success)
+      assertTrue(exists(tempDir.resolve("c/libruntime/generated_code.c")))
+    }
   }
 }
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/listing/TestCLIListing.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/listing/TestCLIListing.scala
deleted file mode 100644
index 6f498f5e8..000000000
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/listing/TestCLIListing.scala
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.daffodil.listing
-
-import org.junit.Test
-import org.apache.daffodil.CLI.Util
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.eof
-import org.apache.daffodil.Main.ExitCode
-
-class TestCLIlisting {
-
-  @Test def test_992_CLI_Executing_Listing_singleTestList(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/Entities.tdml")
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s test -l %s byte_entities_6_08", Util.binPath, testTdmlFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("byte_entities_6_08"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
-  }
-  /*
-  @Test def test_993_CLI_Executing_Listing_listAll() {
-    val shell = ex.spawn("/bin/bash")
-    shell.send("/bin/grep -c 'parserTestCase>' daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/Entities.tdml\n")
-    val num = shell.getCurrentStandardOutContents()
-    shell.send(Util.binPath + " test -l daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/Entities.tdml | tee /dev/tty | wc -l\n")
-    shell.expect(num)
-    shell.send("exit\n")
-    shell.expectClose()
-  }
-
-  @Test def test_999_CLI_Executing_Listing_listRegex01() {
-    val cmd = ""Util.binPath + " test -l --regex daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml "escape_entry4-\\d+"\n"""
-    val shell = Util.start(cmd)
-    shell.expect("escape_entry4-20")
-    shell.expect("escape_entry4-21")
-    shell.send("exit\n")
-    shell.expectClose()
-  }
-
-  @Test def test_1000_CLI_Executing_Listing_listRegex02() {
-    val cmd = Util.binPath + " test -l --regex daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml 'escape_entryb-\\d+'\n"
-    val shell = Util.start(cmd)
-    val output = shell.getCurrentStandardOutContents()
-    if (output != ""){
-      throw new Exception("Output does not match expected.")
-    }
-    shell.send("exit\n")
-    shell.expectClose()
-  }
-*/
-
-  @Test def test_1016_CLI_Executing_Listing_listVerbose(): Unit = {
-    val tdmlFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/assertions/assert.tdml")
-    val testTdmlFile = if (Util.isWindows) Util.cmdConvert(tdmlFile) else tdmlFile
-    val shell = Util.start("")
-
-    try {
-      shell.sendLine(String.format("%s test -l --regex %s assertPattern.*", Util.binPath, testTdmlFile))
-      shell.expect(contains("assertPatternAndExp"))
-
-      shell.sendLine(String.format("%s test -l -i --regex %s assertPattern.*", Util.binPath, testTdmlFile))
-      shell.expect(contains("assertPatternAndExp              s2                e3         Section 7 - Assert Schema Error for Expression/Pattern - DFDL-7-047R"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
-  }
-}
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala
index 8cd92fc9f..4d5a30b5d 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala
@@ -17,246 +17,134 @@
 
 package org.apache.daffodil.parsing
 
+import java.nio.charset.StandardCharsets.UTF_8
+
+import org.apache.commons.io.FileUtils
+
 import org.junit.Assert._
 import org.junit.Test
 
-import java.io.File
-import org.apache.daffodil.CLI.Util
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.eof
+import org.apache.daffodil.CLI.Util._
 import org.apache.daffodil.Main.ExitCode
 
 class TestCLIparsing {
 
-  val output1 = Util.getExpectedString("output1.txt")
-  val output1_nopretty = Util.getExpectedString("output1_nopretty.txt")
-  val output2 = Util.getExpectedString("output2.txt")
-  val output4 = Util.getExpectedString("output4.txt")
-  val output6 = Util.getExpectedString("output6.txt")
-  val output8 = Util.getExpectedString("output8.txt")
-  val output9 = Util.getExpectedString("output9.txt")
-  val output10 = Util.getExpectedString("output10.txt")
-  val output12 = Util.getExpectedString("output12.txt")
-
   @Test def test_3677_CLI_Parsing_elementFormDefault_qualified(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/elementFormDefaultQualified.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/elementFormDefaultQualified.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo strng| %s parse -s %s -r s1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:e1>strng</tns:e1>"))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r s1") { cli =>
+      cli.sendLine("strng", inputDone = true)
+      cli.expect("<tns:e1>strng</tns:e1>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_3678_CLI_Parsing_elementFormDefault_unqualified(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/elementFormDefaultUnqualified.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/elementFormDefaultUnqualified.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo strng| %s parse -s %s -r s1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("<e1>strng</e1>"))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r s1") { cli =>
+      cli.sendLine("strng", inputDone = true)
+      cli.expect("<e1>strng</e1>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_2358_CLI_Parsing_SimpleParse_stdOut_extVars(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+    val config = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
-    val configFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
-    val (testSchemaFile, testConfigFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(configFile)) else (schemaFile, configFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0| %s parse -s %s -r row -D\"{http://example.com}var1=99\" -c %s", Util.binPath, testSchemaFile, testConfigFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:row xmlns:tns=\"http://example.com\">"))
-      shell.expect(contains("<cell>99</cell>"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r row -D{http://example.com}var1=99 -c $config") { cli =>
+      cli.sendLine("0", inputDone = true)
+      cli.expect("<tns:row xmlns:tns=\"http://example.com\">")
+      cli.expect("<cell>99</cell>")
+    } (ExitCode.Success)
   }
 
   @Test def test_3507_CLI_Parsing_SimpleParse_SaveParser_extVars(): Unit = {
+    withTempFile { parser =>
+      val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+      val config = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
+
+      runCLI(args"-v save-parser -s $schema -r row -c $config $parser") { cli =>
+        cli.expectErr("[info] Time (saving)")
+      } (ExitCode.Success)
+
+      runCLI(args"parse -P $parser -D{http://example.com}var1=99") { cli =>
+        cli.sendLine("0", inputDone = true)
+        cli.expect("<tns:row xmlns:tns=\"http://example.com\">")
+        cli.expect("<cell>99</cell>")
+      } (ExitCode.Success)
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
-    val configFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
-    val (testSchemaFile, testConfigFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(configFile)) else (schemaFile, configFile)
-
-    val savedParser = "test_3507.xsd.bin"
-    val parserFile = new File(savedParser)
-
-    val shell = Util.start("")
-
-    try {
-      var cmd = String.format("%s -v save-parser -s %s -r row -c %s %s", Util.binPath, testSchemaFile, testConfigFile, savedParser)
-      shell.sendLine(cmd)
-      shell.expectIn(1, (contains("[info] Time (saving)")))
-      assertTrue("save-parser failed", parserFile.exists())
-
-      shell.sendLine();
-      cmd = String.format("echo 0| %s parse --parser %s -D\"{http://example.com}var1=99\"\n", Util.binPath, savedParser)
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:row xmlns:tns=\"http://example.com\">"))
-      shell.expect(contains("<cell>99</cell>"))
-
-      cmd = String.format("echo 0| %s parse --parser %s -D\"{http://example.com}var1=55\"", Util.binPath, savedParser)
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:row xmlns:tns=\"http://example.com\">"))
-      shell.expect(contains("<cell>55</cell>"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-      if (parserFile.exists()) parserFile.delete()
+      runCLI(args"parse -P $parser -D{http://example.com}var1=55") { cli =>
+        cli.sendLine("0", inputDone = true)
+        cli.expect("<tns:row xmlns:tns=\"http://example.com\">")
+        cli.expect("<cell>55</cell>")
+      } (ExitCode.Success)
     }
   }
 
   @Test def test_2360_CLI_Parsing_SimpleParse_stdOut_extVars2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+    val config = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
-    val configFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
-    val (testSchemaFile, testConfigFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(configFile)) else (schemaFile, configFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse -s %s -r row2 -c %s", Util.binPath, testSchemaFile, testConfigFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output12))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r row2 -c $config") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expect("<cell>-9</cell>")
+      cli.expect("<cell>-2</cell>")
+      cli.expect("<cell>-8</cell>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_3506_CLI_Parsing_SimpleParse_extVars2(): Unit = {
+    withTempFile { parser =>
+      val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val savedParser = "test_3506.xsd.bin"
-    val parserFile = new File(savedParser)
-
-    val shell = Util.start("")
+      runCLI(args"-v save-parser -s $schema -r row $parser") { cli =>
+        cli.expectErr("[info] Time (saving)")
+      } (ExitCode.Success)
 
-    try {
-      var cmd = String.format("%s -v save-parser -s %s -r row %s", Util.binPath, testSchemaFile, savedParser)
-      shell.sendLine(cmd)
-      shell.expectIn(1, (contains("[info] Time (saving)")))
-      assertTrue("save-parser failed", parserFile.exists())
+      runCLI(args"parse --parser $parser") { cli =>
+        cli.sendLine("0", inputDone = true)
+        cli.expect("<tns:row xmlns:tns=\"http://example.com\">")
+        cli.expect("<cell>-1</cell>")
+      } (ExitCode.Success)
 
-      cmd = String.format("echo 0| %s parse --parser %s", Util.binPath, savedParser)
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:row xmlns:tns=\"http://example.com\">"))
-      shell.expect(contains("<cell>-1</cell>"))
-
-      cmd = String.format("echo 0| %s parse --parser %s -D\"{http://example.com}var1=55\"", Util.binPath, savedParser)
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:row xmlns:tns=\"http://example.com\">"))
-      shell.expect(contains("<cell>55</cell>"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-      if (parserFile.exists()) parserFile.delete()
+      runCLI(args"parse --parser $parser -D{http://example.com}var1=55") { cli =>
+        cli.sendLine("0", inputDone = true)
+        cli.expect("<tns:row xmlns:tns=\"http://example.com\">")
+        cli.expect("<cell>55</cell>")
+      } (ExitCode.Success)
     }
   }
 
   @Test def test_CLI_Parsing_SimpleParse_extVars_error(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse -s %s -r row2 -DdoesNotExist=1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-
-      shell.expectIn(1, contains("definition not found"))
-      shell.expectIn(1, contains("doesNotExist"))
-
-      Util.expectExitCode(ExitCode.BadExternalVariable, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r row2 -DdoesNotExist=1") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expectErr("definition not found")
+      cli.expectErr("doesNotExist")
+    } (ExitCode.BadExternalVariable)
   }
 
   @Test def test_3227_CLI_Parsing_SimpleParse_DFDL1197_fix(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section12/delimiter_properties/testOptionalInfix.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section12/delimiter_properties/testOptionalInfix.dfdl.xsd")
 
-    try {
-      val cmd = String.format("echo 1/3| %s -vv parse -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-
-      shell.expectIn(1, contains("<Sequence><Separator/><RepMinMax name='s1'>"))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"-vv parse -s $schema") { cli =>
+      cli.sendLine("1/3", inputDone = true)
+      cli.expectErr("<Sequence><Separator/><RepMinMax name='s1'>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_1593_CLI_Parsing_MultifileSchema_noGlobalElem(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_21.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("does not matter") + " | %s parse -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("No global elements"))
-      shell.expectIn(1, contains("multi_base_21.dfdl.xsd"))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_21.dfdl.xsd")
+
+    runCLI(args"parse -s $schema") { cli =>
+      cli.send("does not matter", inputDone = true)
+      cli.expectErr("No global elements")
+      cli.expectErr("multi_base_21.dfdl.xsd")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
-  //  See comment in DFDL-952
+  //  See comment in DAFFODIL-952
   //
   //  Also note that this test is important in showing the expected existence
   //  and ordering of XML namespace prefix mappings. Daffodil ensures
@@ -265,1124 +153,535 @@ class TestCLIparsing {
   //  verifies the expected output. If this test fails, it likely means we've
   //  broken our attempts to create consistent prefix mappings.
   @Test def test_1585_CLI_Parsing_MultifileSchema_methodImportSameDir(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_14.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("test") + "| %s parse -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output9))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_14.dfdl.xsd")
+
+    runCLI(args"parse -s $schema") { cli =>
+      cli.send("test", inputDone = true)
+      cli.expect("""<base14:rabbitHole xmlns:a14="http://a14.com" xmlns:b14="http://b14.com" xmlns:base14="http://baseSchema.com">""")
+      cli.expect("<a14:nestSequence>")
+      cli.expect("<b14:nest>test</b14:nest>")
+    } (ExitCode.Success)
   }
 
   @Test def test_1586_CLI_Parsing_MultifileSchema_methodIncludeSameDir(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_15.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("test") + "| %s parse -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-
-      shell.expect(contains(output10))
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_15.dfdl.xsd")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema") { cli =>
+      cli.send("test", inputDone = true)
+      cli.expect("<rabbitHole>")
+      cli.expect("<nest>test</nest>")
+    } (ExitCode.Success)
   }
 
   @Test def test_1587_CLI_Parsing_MultifileSchema_methodImportSameDir2(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_16.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("test") + "| %s parse -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_16.dfdl.xsd")
 
-      shell.expect(contains(output10))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema") { cli =>
+      cli.send("test", inputDone = true)
+      cli.expect("<rabbitHole>")
+      cli.expect("<nest>test</nest>")
+    } (ExitCode.Success)
   }
 
   @Test def test_1317_IBMCompatibility_ABC_test_ibm_abc_cli(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/ABC_IBM.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/ABC_IBM.dfdl.xsd")
 
-    try {
-      val cmd = String.format("echo abcabcabc| %s parse -s %s -r ABC", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-
-      shell.expect(contains(output8))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r ABC") { cli =>
+      cli.sendLine("abcabcabc", inputDone = true)
+      cli.expect("<Container>")
+      cli.expect("<c>c</c>")
+    } (ExitCode.Success)
   }
 
   @Test def test_977_CLI_Parsing_SimpleParse_stdOut(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse -s %s -r matrix", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-
-      shell.expect(contains(output1))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r matrix") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_978_CLI_Parsing_SimpleParse_outFile(): Unit = {
-    val tmp_filename: String = (System.currentTimeMillis / 1000).toString()
-    val file = new File(tmp_filename)
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse -s %s -r matrix -o %s", Util.binPath, testSchemaFile, tmp_filename)
-      shell.sendLine(cmd)
-
-      val catCmd = if (Util.isWindows) "type" else "cat"
-      val openCmd = String.format("%s %s", catCmd, tmp_filename)
-
-      shell.sendLine(openCmd)
-      shell.expect(contains("<tns:cell>2</tns:cell>"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-      assertTrue("Failed to remove temporary file: %s".format(file), file.delete)
+    withTempFile { output =>
+      val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+      runCLI(args"parse -s $schema -r matrix -o $output") { cli =>
+        cli.sendLine("0,1,2", inputDone = true)
+      } (ExitCode.LeftOverData)
+
+      val res = FileUtils.readFileToString(output.toFile, UTF_8)
+      assertTrue(res.contains("<tns:cell>2</tns:cell>"))
     }
   }
 
   @Test def test_979_CLI_Parsing_SimpleParse_inFile(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s parse -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output1))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"parse -s $schema -r matrix $input") { cli =>
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.Success)
   }
 
   @Test def test_980_CLI_Parsing_SimpleParse_stOutDash(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s parse -s %s -r matrix -o - %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output1))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"parse -s $schema -r matrix -o - $input") { cli =>
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.Success)
   }
 
   @Test def test_981_CLI_Parsing_SimpleParse_stdInDash(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    try {
-      val cmd = String.format("echo 0,1,2,3| %s parse -s %s -r matrix -", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output2))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r matrix -") { cli =>
+      cli.sendLine("0,1,2,3", inputDone = true)
+      cli.expect("<tns:cell>3</tns:cell>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_983_CLI_Parsing_SimpleParse_verboseMode(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    try {
-      shell.sendLine(String.format("echo 0,1| %s -v parse -s %s -r matrix -", Util.binPath, testSchemaFile))
-      shell.expectIn(1, contains("[info]"))
+    runCLI(args"-v parse -s $schema -r matrix -") { cli =>
+      cli.sendLine("0,1", inputDone = true)
+      cli.expectErr("[info]")
+    } (ExitCode.LeftOverData)
 
-      shell.sendLine(String.format("echo 0,1| %s -vv parse -s %s -r matrix -", Util.binPath, testSchemaFile))
-      shell.expectIn(1, contains("[debug]"))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"-vv parse -s $schema -r matrix -") { cli =>
+      cli.sendLine("0,1", inputDone = true)
+      cli.expectErr("[debug]")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_984_CLI_Parsing_negativeTest(): Unit = {
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2,3| %s parse", Util.binPath)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("There should be exactly one of the following options: schema, parser"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse") { cli =>
+      cli.sendLine("0,1,2,3", inputDone = true)
+      cli.expectErr("There should be exactly one of the following options: schema, parser")
+    } (ExitCode.Usage)
   }
 
   @Test def test_985_CLI_Parsing_SimpleParse_defaultRoot(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2,3| %s parse -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output2))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema") { cli =>
+      cli.sendLine("0,1,2,3", inputDone = true)
+      cli.expect("<tns:cell>3</tns:cell>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_988_CLI_Parsing_SimpleParse_specifiedRoot(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
-
-    try {
-      //val expected = """<tns:hcp2 xmlns:tns="http://www.example.org/example1/">12</tns:hcp2>"""
-      val cmd = String.format("echo 12| %s parse -s %s -r hcp2", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:hcp2"))
-      shell.expect(contains("12"))
-      shell.expect(contains("</tns:hcp2>"))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r hcp2") { cli =>
+      cli.sendLine("12", inputDone = true)
+      cli.expect("<tns:hcp2")
+      cli.expect("12")
+      cli.expect("</tns:hcp2>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_996_CLI_Parsing_negativeTest04(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 12| %s parse -s %s -r unknown", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("No root element found for unknown in any available namespace"))
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r unknown") { cli =>
+      cli.sendLine("12", inputDone = true)
+      cli.expectErr("No root element found for unknown in any available namespace")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   @Test def test_997_CLI_Parsing_multSchemas(): Unit = {
-    val schemaFile1 = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val schemaFile2 = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
-    val (testSchemaFile1, testSchemaFile2) = if (Util.isWindows) (Util.cmdConvert(schemaFile1), Util.cmdConvert(schemaFile2)) else (schemaFile1, schemaFile2)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 12| %s parse -s %s -s %s  -r hcp2", Util.binPath, testSchemaFile1, testSchemaFile2)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Bad arguments for option 'schema'"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema1 = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val schema2 = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
+
+    runCLI(args"parse -s $schema1 -s $schema2 -r hcp2") { cli =>
+      cli.sendLine("12", inputDone = true)
+      cli.expectErr("Bad arguments for option 'schema'")
+    } (ExitCode.Usage)
   }
 
   @Test def test_3661_CLI_Parsing_badSchemaPath(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/doesnotexist.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 12| %s parse -s %s -r root", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Bad arguments for option 'schema'"))
-      shell.expectIn(1, contains("Could not find file or resource"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/doesnotexist.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r root") { cli =>
+      cli.sendLine("12", inputDone = true)
+      cli.expectErr("Bad arguments for option 'schema'")
+      cli.expectErr("Could not find file or resource")
+    } (ExitCode.Usage)
   }
 
   @Test def test_1002_CLI_Parsing_negativeTest03(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse -P parserThatDoesNotExist", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      if (Util.isWindows) {
-        shell.expectIn(1, contains("parserThatDoesNotExist (The system cannot find the file specified)"))
-      } else {
-        shell.expectIn(1, contains("parserThatDoesNotExist (No such file or directory)"))
-      }
 
-      Util.expectExitCode(ExitCode.FileNotFound, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -P parserThatDoesNotExist") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expectErr("parserThatDoesNotExist")
+    } (ExitCode.FileNotFound)
   }
 
   @Test def test_1003_CLI_Parsing_SimpleParse_emptyNamespace(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input7.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s parse -s %s -r {}address %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output4))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input7.txt")
+
+    runCLI(args"parse -s $schema -r {}address $input") { cli =>
+      cli.expect("<address>")
+    } (ExitCode.Success)
   }
 
   @Test def test_1004_CLI_Parsing_SimpleParse_namespaceUsed(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input8.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s parse -s %s -r {target}matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output6))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input8.txt")
+
+    runCLI(args"parse -s $schema -r {target}matrix $input") { cli =>
+      cli.expect("""<tns:matrix xmlns:tns="target">""")
+      cli.expect("<cell>14</cell>")
+    } (ExitCode.Success)
   }
 
   @Test def test_2615_CLI_Parsing_SimpleParse_namespaceUsedLongOpt(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input8.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s parse -s %s --root {target}matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output6))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input8.txt")
+
+    runCLI(args"parse -s $schema --root {target}matrix $input") { cli =>
+      cli.expect("""<tns:matrix xmlns:tns="target">""")
+      cli.expect("<cell>14</cell>")
+    } (ExitCode.Success)
   }
 
   @Test def test_1005_CLI_Parsing_SimpleParse_rootPath(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.startNoConvert("")
-
-    try {
-      //val expected = """<tns:hcp2 xmlns:tns="http://www.example.org/example1/">12</tns:hcp2>"""
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r hcp2 -p /", Util.binPath, testSchemaFile)
-
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:hcp2 xmlns:tns=\"http://www.example.org/example1/\">"))
-      shell.expect(contains("12"))
-      shell.expect(contains("</tns:hcp2>"))
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r hcp2 -p /") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("<tns:hcp2 xmlns:tns=\"http://www.example.org/example1/\">")
+      cli.expect("12")
+      cli.expect("</tns:hcp2>")
+    } (ExitCode.Success)
   }
 
   @Test def test_1015_CLI_Parsing_SimpleParse_defaultRootMultSchema(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input7.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s parse -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output4))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input7.txt")
+
+    runCLI(args"parse -s $schema $input") { cli =>
+      cli.expect("<address>")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleSchema_basicTest_validationOn(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse -s %s -r matrix --validate on", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output1))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r matrix --validate on") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleSchema_basicTest_validation_missing_mode(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse -s %s -r matrix --validate", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Bad arguments"))
-      shell.expectIn(1, contains("validate"))
-      shell.expectIn(1, contains("exactly one argument"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r matrix --validate") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expectErr("Bad arguments")
+      cli.expectErr("validate")
+      cli.expectErr("exactly one argument")
+    } (ExitCode.Usage)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleSchema_basicTest_validationLimited(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse -s %s -r matrix --validate limited", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output1))
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r matrix --validate limited") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleSchema_basicTest_validationOff(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse -s %s -r matrix --validate off", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output1))
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r matrix --validate off") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleSchema_basicTest_validationFooBar(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse --validate FooBar -s %s -r matrix", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("FooBar"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
-  }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-  /*
-  //On hold until I implement a way to set the classpath before executing
-  @Test def test_1313_CLI_Parsing_assertionFailure() {
-    val cmd = "echo unacceptable| " + Util.binPath + " parse -s daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_B_08.dfdl.xsd -s daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_C_08.dfdl.xsd --root bElem2\n"
-    val shell = Util.start(cmd)
-    shell.expect(contains("Parse Error: Assertion failed. Assertion failed for dfdl:checkConstraints(.)"))
-
-    shell.send("exit\n")
-    shell.expect(eof)
-    shell.close()
+    runCLI(args"parse --validate FooBar -s $schema -r matrix") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expectErr("FooBar")
+    } (ExitCode.Usage)
   }
-*/
 
   @Test def test_1319_CLI_Parsing_invalidElementSDE(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/ABC_IBM_invalid.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo ababababbaacccccb| %s parse -s %s -r ABC", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("'fixed' is not a valid"))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/ABC_IBM_invalid.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r ABC") { cli =>
+      cli.sendLine("ababababbaacccccb", inputDone = true)
+      cli.expectErr("'fixed' is not a valid")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   @Test def test_1346_CLI_Parsing_SimpleParse_defaultRootMultSchemaMultiple(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input7.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val cmd = String.format("%s parse -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input7.txt")
     for (x <- 1 to 10) {
-      val shell = Util.start("")
-
-      try {
-        println("Run " + x + " of 10")
-        shell.sendLine(cmd)
-        shell.expect(contains(output4))
-
-        Util.expectExitCode(ExitCode.Success, shell)
-        shell.sendLine("exit")
-        shell.expect(eof)
-      } finally {
-        shell.close()
-      }
+      runCLI(args"parse -s $schema $input") { cli =>
+        cli.expect("<address>")
+      } (ExitCode.Success)
     }
   }
 
   @Test def test_1386_CLI_Parsing_negativeTest05(): Unit = {
-    val cmd = String.format("echo 12| %s", Util.binPath)
-    val shell = Util.start("")
-
-    try {
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Subcommand required"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"") { cli =>
+      cli.sendLine("12", inputDone = true)
+      cli.expectErr("Subcommand required")
+    } (ExitCode.Usage)
   }
 
   @Test def test_1971_CLI_Parsing_traceMode01(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_15.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo test| %s -t parse -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("parser: <Element name='rabbitHole'>"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_15.dfdl.xsd")
+
+    runCLI(args"-t parse -s $schema") { cli =>
+      cli.sendLine("test", inputDone = true)
+      cli.expect("parser: <Element name='rabbitHole'>")
+    } (ExitCode.Success)
   }
 
   @Test def test_1973_CLI_Parsing_traceMode03(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 0,1,2,3,,,,| %s -t parse -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Left over data. Consumed 56 bit(s) with at least"))
-      shell.expectIn(1, contains("Left over data (Hex) starting at byte 8 is: ("))
-      shell.expectIn(1, contains("Left over data (UTF-8) starting at byte 8 is: ("))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-      //assert(shell.getExitValue() == 1)
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"-t parse -s $schema") { cli =>
+      cli.sendLine("0,1,2,3,,,,", inputDone = true)
+      cli.expectErr("Left over data. Consumed 56 bit(s) with at least")
+      cli.expectErr("Left over data (Hex) starting at byte 8 is: (")
+      cli.expectErr("Left over data (UTF-8) starting at byte 8 is: (")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_1941_CLI_Parsing_SimpleParse_leftOverData(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("echo 1,2,3,4,,,| %s parse -s %s -r matrix", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Left over data. Consumed 56 bit(s) with at least"))
-      shell.expectIn(1, contains("Left over data (Hex) starting at byte 8 is: ("))
-      shell.expectIn(1, contains("Left over data (UTF-8) starting at byte 8 is: ("))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r matrix") { cli =>
+      cli.sendLine("1,2,3,4,,,", inputDone = true)
+      cli.expectErr("Left over data. Consumed 56 bit(s) with at least")
+      cli.expectErr("Left over data (Hex) starting at byte 8 is: (")
+      cli.expectErr("Left over data (UTF-8) starting at byte 8 is: (")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_CLI_Parsing_BitParse_LSBPartialByte_leftOverData(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/bits_parsing.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("stri") + "| %s parse -s %s -r lsbPartialByte", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Left over data. Consumed 10 bit(s) with at least 16 bit(s) remaining."
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/bits_parsing.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r lsbPartialByte") { cli =>
+      cli.send("stri", inputDone = true)
+      cli.expectErr("Left over data. Consumed 10 bit(s) with at least 16 bit(s) remaining."
         + "\nLeft over data starts with partial byte. Left over data (Binary) at byte 2 is: (0b011101xx)"
         + "\nLeft over data (Hex) starting at byte 3 is: (0x7269...)"
-        + "\nLeft over data (UTF-8) starting at byte 3 is: (ri...)"))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+        + "\nLeft over data (UTF-8) starting at byte 3 is: (ri...)")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_CLI_Parsing_BitParse_MSBPartialByte_leftOverData(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/bits_parsing.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("stri") + "| %s parse -s %s -r msbPartialByte", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Left over data. Consumed 10 bit(s) with at least 16 bit(s) remaining."
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/bits_parsing.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r msbPartialByte") { cli =>
+      cli.send("stri", inputDone = true)
+      cli.expectErr("Left over data. Consumed 10 bit(s) with at least 16 bit(s) remaining."
         + "\nLeft over data starts with partial byte. Left over data (Binary) at byte 2 is: (0bxx110100)"
         + "\nLeft over data (Hex) starting at byte 3 is: (0x7269...)"
-        + "\nLeft over data (UTF-8) starting at byte 3 is: (ri...)"))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+        + "\nLeft over data (UTF-8) starting at byte 3 is: (ri...)")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_CLI_Parsing_BitParse_MSBFullByte_leftOverData(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/bits_parsing.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("stri") + "| %s parse -s %s -r msbFullByte", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Left over data. Consumed 16 bit(s) with at least 16 bit(s) remaining."
-        + "\nLeft over data (Hex) starting at byte 3 is: (0x7269...)"
-        + "\nLeft over data (UTF-8) starting at byte 3 is: (ri...)"))
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/bits_parsing.dfdl.xsd")
 
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r msbFullByte") { cli =>
+      cli.send("stri", inputDone = true)
+      cli.expectErr("Left over data. Consumed 16 bit(s) with at least 16 bit(s) remaining."
+        + "\nLeft over data (Hex) starting at byte 3 is: (0x7269...)"
+        + "\nLeft over data (UTF-8) starting at byte 3 is: (ri...)")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_DFDL_714(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/global_element.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/test_DFDL-714.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s parse -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:elem xmlns:tns=\"http://baseSchema.com\">"))
-      shell.expect(contains("<content"))
-      shell.expect(contains("Hello World"))
-      shell.expect(contains("</tns:elem>"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/global_element.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/test_DFDL-714.txt")
+
+    runCLI(args"parse -s $schema $input") { cli =>
+      cli.expect("<tns:elem xmlns:tns=\"http://baseSchema.com\">")
+      cli.expect("<content")
+      cli.expect("Hello World")
+      cli.expect("</tns:elem>")
+    } (ExitCode.Success)
   }
 
   @Test def test_DFDL_1203_schema_from_jar(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/global_element.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/test_DFDL-714.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> Util.daffodilPath("daffodil-cli/target/scala-2.10/*")))
-
-    try {
-      val cmd = String.format("%s parse -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:elem xmlns:tns=\"http://baseSchema.com\">"))
-      shell.expect(contains("<content"))
-      shell.expect(contains("Hello World"))
-      shell.expect(contains("</tns:elem>"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/global_element.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/test_DFDL-714.txt")
+
+    runCLI(args"parse -s $schema $input") { cli =>
+      cli.expect("<tns:elem xmlns:tns=\"http://baseSchema.com\">")
+      cli.expect("<content")
+      cli.expect("Hello World")
+      cli.expect("</tns:elem>")
+    } (ExitCode.Success)
   }
 
   @Test def test_3606_CLI_Parsing_SimpleParse_largeInfoset(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    try {
+    runCLI(args"parse -s $schema -r matrix") { cli =>
       val longInput = "0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1 [...]
-      val cmd = String.format("echo %s| %s parse -s %s -r matrix", longInput, Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-
-      val result = shell.expect(contains("<tns:row")).getBefore()
-      println(result)
+      cli.sendLine(longInput, inputDone = true)
+      val result = cli.expect("<tns:row").getBefore()
       if (result.contains("""<tns:matrix xmlns:tns="http://www.example.org/example1/"><tns:matrix xmlns:tns="http://www.example.org/example1/">""")) {
         throw new Exception("Error - Root has been duplicated")
       }
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_CLI_Parsing_built_in_formats(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_04.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input6.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_04.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input6.txt")
 
-    try {
-      val cmd = String.format("%s parse -s %s -r e %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-
-      shell.expectIn(1, contains("Schema Definition Warning"))
-      shell.expectIn(1, contains("edu/illinois/ncsa/daffodil/xsd/built-in-formats.xsd"))
-      shell.expectIn(1, contains("org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("quit")
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r e $input") { cli =>
+      cli.expectErr("Schema Definition Warning")
+      cli.expectErr("edu/illinois/ncsa/daffodil/xsd/built-in-formats.xsd")
+      cli.expectErr("org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd")
+    } (ExitCode.Success)
   }
 
-  // These DAFFODIL_JAVA_OPTS values change the Java defaults classes like
-  // SAXParserFactory and SchemaFactory to be Java's internal classes instead
-  // of those provided by dependencies (e.g. Xerces) included with Daffodil.
-  // Some places require dependency version of these classes. This test ensures
-  // that we override defaults when necesssary
   @Test def test_CLI_Parsing_JavaDefaults(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val java_opts = Map("DAFFODIL_JAVA_OPTS" ->
-      ("-Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl " +
-        "-Djavax.xml.xml.validation.SchemaFactory=com/sun/org/apache/xerces/internal/jaxp/validation/XMLSchemaFactory"))
-
-    val shell = Util.start("", envp = java_opts)
-
-    try {
-      val cmd = String.format("echo 0,1,2| %s parse -s %s -r matrix", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-
-      shell.expect(contains(output1))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    withSysProp("javax.xml.parsers.SAXParserFactory" -> "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl") {
+      withSysProp("javax.xml.xml.validation.SchemaFactory" -> "com/sun/org/apache/xerces/internal/jaxp/validation/XMLSchemaFactory") {
+        runCLI(args"parse -s $schema -r matrix") { cli =>
+          cli.sendLine("0,1,2", inputDone = true)
+          cli.expect("<tns:cell>2</tns:cell>")
+        } (ExitCode.LeftOverData)
+      }
     }
   }
 
   @Test def test_XXX_CLI_Parsing_Stream_01(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_02.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("123") + "| %s parse --stream -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("<a>1</a>"))
-      shell.expect(contains("<a>2</a>"))
-      shell.expect(contains("<a>3</a>"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_02.dfdl.xsd")
+
+    runCLI(args"parse --stream -s $schema") { cli =>
+      cli.send("123", inputDone = true)
+      cli.expect("<a>1</a>")
+      cli.expect("<a>2</a>")
+      cli.expect("<a>3</a>")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Parsing_Stream_02(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_02.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("123ab") + "| %s parse --stream -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("<a>1</a>"))
-      shell.expect(contains("<a>2</a>"))
-      shell.expect(contains("<a>3</a>"))
-      shell.expectIn(1, contains("Left over data after consuming 0 bits while streaming."))
-      shell.expectIn(1, contains("Stopped after consuming 24 bit(s) with at least 16 bit(s) remaining."))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema_02.dfdl.xsd")
+
+    runCLI(args"parse --stream -s $schema") { cli =>
+      cli.send("123ab", inputDone = true)
+      cli.expect("<a>1</a>")
+      cli.expect("<a>2</a>")
+      cli.expect("<a>3</a>")
+      cli.expectErr("Left over data after consuming 0 bits while streaming.")
+      cli.expectErr("Stopped after consuming 24 bit(s) with at least 16 bit(s) remaining.")
+    } (ExitCode.LeftOverData)
   }
 
   @Test def test_CLI_Parsing_XCatalog_Resolution_Failure(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/xcatalog_import_failure.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-
-    val xcatalogFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/xcatalog_invalid.xml")
-    val testXcatalogFile = if (Util.isWindows) Util.cmdConvert(xcatalogFile) else xcatalogFile
-
-    val DAFFODIL_JAVA_OPTS = Map("DAFFODIL_JAVA_OPTS" -> ("-Dxml.catalog.files=" + testXcatalogFile + " -Xms256m -Xmx2048m -Dfile.encoding=UTF-8"))
-
-    val shell = Util.start("", envp = DAFFODIL_JAVA_OPTS)
-
-    try {
-      val cmd = String.format(Util.echoN("X") + "| %s parse -s %s", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/xcatalog_import_failure.dfdl.xsd")
+    val xcatalog = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/xcatalog_invalid.xml")
 
-      shell.expectIn(1, contains("Schema Definition Error"))
-      if (Util.isWindows) {
-        shell.expectIn(1, contains("\\this\\path\\does\\not\\exist"))
-        shell.expectIn(1, contains("The system cannot find the path specified"))
-      } else {
-        shell.expectIn(1, contains("/this/path/does/not/exist"))
-        shell.expectIn(1, contains("No such file or directory"))
-      }
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
+    withSysProp("xml.catalog.files" -> xcatalog.toAbsolutePath.toString) {
+      runCLI(args"parse -s $schema") { cli =>
+        cli.send("X", inputDone = true)
+        cli.expectErr("Schema Definition Error")
+        cli.expectErr("non_existent_file.xml")
+      } (ExitCode.UnableToCreateProcessor)
     }
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleParse_w3cdom(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("Hello") + "| %s parse -I w3cdom -s %s -r e1", Util.binPath, testSchemaFile)
-
-      shell.sendLine(cmd)
-      shell.expect(contains("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>"""))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -I w3cdom -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+      cli.expect("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>""")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleParse_jdom(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("Hello") + "| %s parse -I jdom -s %s -r e1", Util.binPath, testSchemaFile)
-
-      shell.sendLine(cmd)
-      shell.expect(contains("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>"""))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -I jdom -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+      cli.expect("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>""")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleParse_scala_xml(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("Hello") + "| %s parse -I scala-xml -s %s -r e1", Util.binPath, testSchemaFile)
-
-      shell.sendLine(cmd)
-      shell.expect(contains("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>"""))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -I scala-xml -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+      cli.expect("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>""")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleParse_json(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("Hello") + "| %s parse -I json -s %s -r e1", Util.binPath, testSchemaFile)
-
-      shell.sendLine(cmd)
-      shell.expect(contains(""""e1": "Hello""""))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -I json -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+      cli.expect(""""e1": "Hello"""")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleParse_sax(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("Hello") + "| %s parse -I sax -s %s -r e1", Util.binPath, testSchemaFile)
-
-      shell.sendLine(cmd)
-      shell.expect(contains("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>"""))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -I sax -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+      cli.expect("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>""")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleParse_exi(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("Hello") + "| %s parse -I exi -s %s -r e1 | %s unparse -I exi -s %s -r e1", Util.binPath, testSchemaFile, Util.binPath, testSchemaFile)
-
-      shell.sendLine(cmd)
-      shell.expect(contains("Hello"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -I exi -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Parsing_SimpleParse_exisa(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("Hello") + "| %s parse -I exisa -s %s -r e1 | %s unparse -I exisa -s %s -r e1", Util.binPath, testSchemaFile, Util.binPath, testSchemaFile)
-
-      shell.sendLine(cmd)
-      shell.expect(contains("Hello"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -I exisa -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+    } (ExitCode.Success)
   }
-  @Test def test_CLI_Error_Return_Codes(): Unit = {
-
-    val shell = Util.start("")
-
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    try {
-      val cmd = String.format("%s parse -I scala-xml -s %s -r e1", Util.binPath, "/this/does/not/exist")
 
-      shell.sendLine(cmd)
-      Util.expectExitCode(ExitCode.Usage, shell)
-
-    } finally {
-      shell.close()
-    }
+  @Test def test_CLI_Error_Return_Codes(): Unit = {
+    val schema = path("this.does.not.exist")
 
+    runCLI(args"parse -I scala-xml -s $schema -r e1") { cli =>
+      cli.expectErr("this.does.not.exist")
+    } (ExitCode.Usage)
   }
 
-  @Test def test_2575_DFDLX_Trace_output(): Unit = {  
-    
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/trace_input.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
+  @Test def test_2575_DFDLX_Trace_output(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/trace_input.dfdl.xsd")
 
-    val shell = Util.start("")
-
-    try{
-      
-      val cmd = String.format(Util.echoN("0") + "| %s -v parse -r output -s %s ", Util.binPath, testSchemaFile)
-
-      shell.sendLine(cmd)
-
-      //show the log is happening
-      shell.expectIn(1,contains("dfdlx:trace"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-
-    } finally {
-      shell.close()
-    }
+    runCLI(args"-v parse -r output -s $schema") { cli =>
+      cli.send("0", inputDone = true)
+      cli.expectErr("dfdlx:trace")
+    } (ExitCode.Success)
   }
 
 }
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/performance/TestCLIPerformance.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/performance/TestCLIPerformance.scala
index 42437e10d..ac70caccb 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/performance/TestCLIPerformance.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/performance/TestCLIPerformance.scala
@@ -17,330 +17,162 @@
 
 package org.apache.daffodil.performance
 
-
 import org.junit.Test
-import org.apache.daffodil.CLI.Util
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.eof
+
+import org.apache.daffodil.CLI.Util._
 import org.apache.daffodil.Main.ExitCode
 
 class TestCLIPerformance {
 
   @Test def test_3393_CLI_Performance_2_Threads_2_Times(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance -N 2 -t 2 -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total parse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"performance -N 2 -t 2 -s $schema -r matrix $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Performance_2_Threads_2_Times_sax(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance -I sax -N 2 -t 2 -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total parse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"performance -I sax -N 2 -t 2 -s $schema -r matrix $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Performance_2_Threads_2_Times_exi(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance -I exi -N 2 -t 2 -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total parse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"performance -I exi -N 2 -t 2 -s $schema -r matrix $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Performance_2_Threads_2_Times_exisa(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance -I exisa -N 2 -t 2 -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total parse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"performance -I exisa -N 2 -t 2 -s $schema -r matrix $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_3394_CLI_Performance_3_Threads_20_Times(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance -N 20 -t 3 -s %s -r matrix %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total parse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt")
+
+    runCLI(args"performance -N 20 -t 3 -s $schema -r matrix $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_3395_CLI_Performance_5_Threads_50_Times(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input5.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance -N 50 -t 5 -s %s -r Item2 %s", Util.binPath, testSchemaFile, testInputFile)
-      println(cmd)
-      shell.sendLine(cmd)
-      shell.expect(contains("total parse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input5.txt")
+
+    runCLI(args"performance -N 50 -t 5 -s $schema -r Item2 $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_3396_CLI_Performance_2_Threads_2_Times_Negative(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input5.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance -N 2 -t 2 -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total parse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-      shell.expectIn(1, (contains("error")))
-
-      Util.expectExitCode(ExitCode.PerformanceTestError, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input5.txt")
+
+    runCLI(args"performance -N 2 -t 2 -s $schema $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+      cli.expectErr("error")
+    } (ExitCode.PerformanceTestError)
   }
 
   @Test def test_3641_CLI_Performance_Unparse_2_Threads_2_Times(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance --unparse -N 2 -t 2 -s %s -r e3 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total unparse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
+
+    runCLI(args"performance --unparse -N 2 -t 2 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Performance_Unparse_2_Threads_2_Times_sax(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance --unparse -I sax -N 2 -t 2 -s %s -r e3 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total unparse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
+
+    runCLI(args"performance --unparse -I sax -N 2 -t 2 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Performance_Unparse_2_Threads_2_Times_exi(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.exi")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance --unparse -I exi -N 2 -t 2 -s %s -r e3 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total unparse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.exi")
+
+    runCLI(args"performance --unparse -I exi -N 2 -t 2 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Performance_Unparse_2_Threads_2_Times_exisa(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.exisa")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance --unparse -I exisa -N 2 -t 2 -s %s -r e3 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total unparse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.exisa")
+
+    runCLI(args"performance --unparse -I exisa -N 2 -t 2 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_XXX_CLI_Performance_Unparse_2_Threads_2_Times_null(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance --unparse -I null -N 2 -t 2 -s %s -r e3 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total unparse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
+
+    runCLI(args"performance --unparse -I null -N 2 -t 2 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_3643_CLI_Performance_Unparse_3_Threads_20_Times(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance --unparse -N 20 -t 3 -s %s -r e3 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total unparse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
+
+    runCLI(args"performance --unparse -N 20 -t 3 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_3644_CLI_Performance_Unparse_5_Threads_50_Times(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance --unparse -N 50 -t 5 -s %s -r e3 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total unparse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
+
+    runCLI(args"performance --unparse -N 50 -t 5 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
   }
 
   @Test def test_3642_CLI_Performance_Unparse_2_Threads_2_Times_Negative(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input16.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s performance --unparse -N 2 -t 2 -s %s %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("total unparse time (sec):"))
-      shell.expect(contains("avg rate (files/sec):"))
-      shell.expectIn(1, (contains("error")))
-
-      Util.expectExitCode(ExitCode.PerformanceTestError, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input16.txt")
+
+    runCLI(args"performance --unparse -N 2 -t 2 -s $schema $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+      cli.expectErr("error")
+    } (ExitCode.PerformanceTestError)
   }
 }
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/saving/TestCLISaveParser.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/saving/TestCLISaveParser.scala
index d26508207..5e1a33f3e 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/saving/TestCLISaveParser.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/saving/TestCLISaveParser.scala
@@ -17,253 +17,143 @@
 
 package org.apache.daffodil.saving
 
-import org.junit.Assert._
 import org.junit.Test
-import org.junit.Before
-import org.junit.After
-import org.apache.daffodil.CLI.Util
-import java.io.File
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.eof
+
+import org.apache.daffodil.CLI.Util._
 import org.apache.daffodil.Main.ExitCode
 
 class TestCLISaveParser {
 
-  val output1 = Util.getExpectedString("output1.txt")
-  val output4 = Util.getExpectedString("output4.txt")
-  val output6 = Util.getExpectedString("output6.txt")
-  val output12 = Util.getExpectedString("output12.txt")
-  val savedParserFile = new File("savedParser.xsd.bin")
-
-  @Before def before(): Unit = {
-    savedParserFile.delete
-  }
+  @Test def test_3017_CLI_Saving_SaveParser_simple(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-  @After def after(): Unit = {
-    savedParserFile.delete
-  }
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r matrix $parser") { cli =>
+      } (ExitCode.Success)
 
-  @Test def test_3017_CLI_Saving_SaveParser_simple(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val saveCmd = String.format("%s save-parser -s %s -r matrix %s", Util.binPath, testSchemaFile, savedParserFile.getName())
-      shell.sendLine(saveCmd)
-
-      val cmd = String.format("echo 0,1,2| %s parse --parser %s", Util.binPath, savedParserFile.getName())
-      shell.sendLine(cmd)
-      shell.expect(contains(output1))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
+      runCLI(args"parse --parser $parser") { cli =>
+        cli.sendLine("0,1,2", inputDone = true)
+        cli.expect("<tns:cell>2</tns:cell>")
+      } (ExitCode.LeftOverData)
     }
   }
 
   @Test def test_3018_CLI_Saving_SaveParser_stdout(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
 
-    val shell = Util.start("")
-    val savedParser = "external_variables.dfdl.xsd.bin"
-    val parserFile = new File(savedParser)
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
-    val saveCmd = String.format("%s -v save-parser -s %s > %s\n",
-      Util.binPath,
-      (if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile),
-      savedParser)
-
-    try {
-      shell.send(saveCmd)
-      shell.expectIn(1, (contains("[info] Time")))
-      assertTrue("save-parser failed", parserFile.exists())
-      val parseCmd = String.format("echo 0,1,2| %s parse --parser %s\n", Util.binPath, savedParser)
-      shell.send(parseCmd)
-      shell.expect(contains("<tns:row xmlns:tns=\"http://example.com\">"))
-      shell.expect(contains("<cell>0</cell>"))
-      shell.expect(contains("<cell>-1</cell>"))
-      shell.expect(contains("<cell>-2</cell>"))
-      shell.expect(contains("</tns:row>"))
-      shell.send("exit\n")
-      shell.expect(eof())
-    } finally {
-      shell.close()
-      if (parserFile.exists()) parserFile.delete()
-    }
+    runCLI(args"-v save-parser -s $schema") { cli =>
+      cli.expectErr("[info] Time")
+      cli.expect("DAFFODIL") // check for magic number
+    } (ExitCode.Success)
   }
 
   @Test def test_3019_CLI_Saving_SaveParser_withConfig(): Unit = {
-
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
-    val configFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
-    val (testSchemaFile, testConfigFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(configFile)) else (schemaFile, configFile)
-
-    val shell = Util.start("")
-
-    try {
-      val saveCmd = String.format("%s save-parser -s %s -r row2 -c %s %s", Util.binPath, testSchemaFile, testConfigFile, savedParserFile.getName())
-      shell.sendLine(saveCmd)
-
-      val cmd = String.format("echo 0,1,2| %s parse --parser %s -c %s", Util.binPath, savedParserFile.getName(), testConfigFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output12))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+    val config = path ("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r row2 -c $config $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser -c $config") { cli =>
+        cli.sendLine("0,1,2", inputDone = true)
+        cli.expect("<cell>-9</cell>")
+        cli.expect("<cell>-2</cell>")
+        cli.expect("<cell>-8</cell>")
+      } (ExitCode.LeftOverData)
     }
   }
 
   @Test def test_3020_CLI_Saving_SaveParser_namespaceUsed(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input8.txt")
 
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input8.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r {target}matrix $parser") { cli =>
+      } (ExitCode.Success)
 
-    val shell = Util.start("")
-
-    try {
-      val saveCmd = String.format("%s save-parser -s %s -r {target}matrix %s", Util.binPath, testSchemaFile, savedParserFile.getName())
-      shell.sendLine(saveCmd)
-
-      val cmd = String.format("%s parse --parser %s %s", Util.binPath, savedParserFile.getName(), testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(output6))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit\n")
-      shell.expect(eof())
-    } finally {
-      shell.close()
+      runCLI(args"parse --parser $parser $input") { cli =>
+        cli.expect("<cell>14</cell>")
+      } (ExitCode.Success)
     }
   }
 
   @Test def test_3021_CLI_Saving_SaveParser_path(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val saveCmd = String.format("%s save-parser -s %s -r matrix -p / %s", Util.binPath, testSchemaFile, savedParserFile.getName())
-      shell.sendLine(saveCmd)
-
-      val cmd = String.format("echo 0,1,2| %s parse --parser %s", Util.binPath, savedParserFile.getName())
-      shell.sendLine(cmd)
-      shell.expect(contains(output1))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r matrix -p / $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser") { cli =>
+        cli.sendLine("0,1,2", inputDone = true)
+        cli.expect("<tns:cell>2</tns:cell>")
+      } (ExitCode.LeftOverData)
     }
   }
 
   @Test def test_3022_CLI_Saving_SaveParser_MultSchema(): Unit = {
+    val schema1 = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
+    val schema2 = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd")
 
-    val schemaFile1 = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
-    val schemaFile2 = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd")
-    val (testSchemaFile1, testSchemaFile2) = if (Util.isWindows) (Util.cmdConvert(schemaFile1), Util.cmdConvert(schemaFile2)) else (schemaFile1, schemaFile2)
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s save-parser -s %s -s %s %s", Util.binPath, testSchemaFile1, testSchemaFile2, savedParserFile.getName())
-      shell.sendLine(cmd)
-
-      shell.expectIn(1, contains("Bad arguments for option 'schema'"))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema1 -s $schema2 $parser") { cli =>
+        cli.expectErr("Bad arguments for option 'schema'")
+      } (ExitCode.Usage)
     }
   }
 
   @Test def test_3023_CLI_Saving_SaveParser_verboseMode(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      shell.sendLine(String.format("%s -v save-parser -s %s -r matrix %s", Util.binPath, testSchemaFile, savedParserFile.getName()))
-      shell.expectIn(1, contains("[info]"))
+    withTempFile { parser =>
+      runCLI(args"-v save-parser -s $schema -r matrix $parser") { cli =>
+        cli.expectErr("[info]")
+      } (ExitCode.Success)
 
-      shell.sendLine(String.format("%s -vv save-parser -s %s -r matrix %s", Util.binPath, testSchemaFile, savedParserFile.getName()))
-      shell.expectIn(1, contains("[debug]"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof())
-    } finally {
-      shell.close()
+      runCLI(args"-vv save-parser -s $schema -r matrix $parser") { cli =>
+        cli.expectErr("[debug]")
+      } (ExitCode.Success)
     }
   }
 
-  // See DFDL-1016
-  /*@Test def test_3038_CLI_Saving_SaveParser_namespaceNoRoot() {
-
-    val cmd = Util.binPath + " save-parser -s daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd -r {http://www.example.org/example1/} savedParser.xml\n"
-    val shell = Util.start(cmd, true)
-
-    // Error message needs to be updated with actual message
-    shell.expect(contains("Error - Root is required if namespace is given"))
+  @Test def test_3038_CLI_Saving_SaveParser_namespaceNoRoot(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    shell.send("exit\n")
-    shell.expect(eof())
-    shell.close()
-  }*/
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r {http://www.example.org/example1/} $parser") { cli =>
+        cli.expectErr("Bad arguments for option 'root'")
+        cli.expectErr("{http://www.example.org/example1/}")
+      } (ExitCode.Usage)
+    }
+  }
 
   @Test def test_3039_CLI_Saving_SaveParser_emptyNamespace(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.startNoConvert("")
-
-    try {
-      val saveCmd = String.format("%s save-parser -s %s -r {}matrix -p / %s", Util.binPath, testSchemaFile, savedParserFile.getName())
-      shell.sendLine(saveCmd)
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r {}matrix -p / $parser") { cli =>
+      } (ExitCode.Success)
 
-      val cmd = String.format("echo 0,1,2| %s parse --parser %s", Util.binPath, savedParserFile.getName())
-      shell.sendLine(cmd)
-
-      shell.expect(contains(output1))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
+      runCLI(args"parse --parser $parser") { cli =>
+        cli.sendLine("0,1,2", inputDone = true)
+        cli.expect("<tns:cell>2</tns:cell>")
+      } (ExitCode.LeftOverData)
     }
   }
 
   @Test def test_DFDL_1205_CLI_FullValidation_SavedParser_Incompatible(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input8.txt")
 
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input8.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-    val shell = Util.start("")
-
-    try {
-      val saveCmd = String.format("%s save-parser -s %s -r {target}matrix %s", Util.binPath, testSchemaFile, savedParserFile.getName())
-      shell.sendLine(saveCmd)
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r {target}matrix $parser") { cli =>
+      } (ExitCode.Success)
 
-      val cmd = String.format("%s parse --parser %s --validate on %s", Util.binPath, savedParserFile.getName(), testInputFile)
-      shell.sendLine(cmd)
-
-      shell.expectIn(1, contains("[error]"))
-      shell.expectIn(1, contains("The validation mode must be 'limited' or 'off' when using a saved parser."))
-
-      Util.expectExitCode(ExitCode.Usage, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
+      runCLI(args"parse --parser $parser --validate on $input") { cli =>
+        cli.expectErr("[error]")
+        cli.expectErr("The validation mode must be 'limited' or 'off' when using a saved parser.")
+      } (ExitCode.Usage)
     }
   }
 
@@ -272,173 +162,108 @@ class TestCLISaveParser {
    * compiling. They are a runtime-thing only.
    */
   @Test def test_3508_CLI_Saving_SaveParser_extVars(): Unit = {
-
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val savedParser = "test_3508.xsd.bin"
-    val parserFile = new File(savedParser)
-
-    val shell = Util.start("")
-
-    try {
-      var cmd = String.format("%s -v save-parser -s %s -r row2  %s", Util.binPath, testSchemaFile, savedParser)
-      shell.sendLine(cmd)
-      shell.expectIn(1, (contains("[info] Time (saving)")))
-      assertTrue("save-parser failed", parserFile.exists())
-
-      shell.sendLine();
-      cmd = String.format("""echo 0| %s parse --parser %s -D"{http://example.com}var1=25" "{http://example.com}var3=7" """, Util.binPath, savedParser)
-      shell.sendLine(cmd)
-      shell.expect(contains("<tns:row2 xmlns:tns=\"http://example.com\">"))
-      shell.expect(contains("<cell>25</cell>"))
-      shell.expect(contains("<cell>7</cell>"))
-
-      Util.expectExitCode(ExitCode.LeftOverData, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-      if (parserFile.exists()) parserFile.delete()
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r row2 $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser -D{http://example.com}var1=25 {http://example.com}var3=7") { cli =>
+        cli.sendLine("0", inputDone = true)
+        cli.expect("<tns:row2 xmlns:tns=\"http://example.com\">")
+        cli.expect("<cell>25</cell>")
+        cli.expect("<cell>7</cell>")
+      } (ExitCode.LeftOverData)
     }
   }
 
-  // See DFDL-1147
-  /*@Test def test_3063_CLI_Saving_SaveParser_validate() {
-
-    val cmd = Util.binPath + " save-parser --validate on -s daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd -r validation_check savedParser.xml\n"
-    shell.send(cmd)
-
-    var cmd2 = "echo -ne 'test'| " + Util.binPath + " parse --parser savedParser.xml \n"
-    shell.send(cmd2)
-    shell.expect(contains("[warn] Validation Error: validation_check: cvc-pattern-valid"))
-    shell.expect(contains("[warn] Validation Error: validation_check failed"))
-
-    cmd = Util.binPath + " save-parser --validate -s daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd -r validation_check savedParser.xml\n"
-    shell.send(cmd)
+  @Test def test_3063_CLI_Saving_SaveParser_validate(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd")
 
-    cmd2 = "echo -ne 'test'| " + Util.binPath + " parse --parser savedParser.xml \n"
-    shell.send(cmd2)
-    shell.expect(contains("[warn] Validation Error: validation_check: cvc-pattern-valid"))
-    shell.expect(contains("[warn] Validation Error: validation_check failed"))
+    withTempFile { parser =>
+      runCLI(args"save-parser --validate on -s $schema -r validation_check $parser") { cli =>
+        cli.expectErr("Unknown option 'validate'")
+      } (ExitCode.Usage)
 
-    cmd = Util.binPath + " save-parser --validate limited -s daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd -r validation_check savedParser.xml\n"
-    shell.send(cmd)
+      runCLI(args"save-parser -s $schema -r validation_check $parser") { cli =>
+      } (ExitCode.Success)
 
-    cmd2 = "echo -ne 'test'| " + Util.binPath + " parse --parser savedParser.xml \n"
-    shell.send(cmd2)
-    shell.expect(contains("[warn] Validation Error: validation_check failed"))
+      runCLI(args"parse --validate limited -P $parser") { cli =>
+        cli.send("test", inputDone = true)
+        cli.expectErr("[error] Validation Error")
+        cli.expectErr("ex:validation_check failed")
+        cli.expectErr("[0-8]+")
+      } (ExitCode.ParseError)
 
-    cmd = Util.binPath + " save-parser --validate off -s daffodil-cli/src/it/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd -r validation_check savedParser.xml\n"
-    shell.send(cmd)
+      runCLI(args"parse --validate on -P $parser") { cli =>
+        cli.send("test", inputDone = true)
+        cli.expectErr("validation mode must be 'limited' or 'off' when using a saved parser.")
+      } (ExitCode.Usage)
+    }
+  }
 
-    cmd2 = "echo -ne 'test'| " + Util.binPath + " parse --parser savedParser.xml \n"
-    shell.send(cmd2)
+  // DAFFODIL-1141
+  /*@Test*/ def test_3036_CLI_Saving_SaveParser_debug(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    shell.send("exit\n")
-    shell.expect(eof())
-    shell.close()
-  }*/
+    withTempFile { parser =>
+      runCLI(args"-d save-parser -s $schema -r matrix $parser") { cli =>
+        cli.expectErr("Some error about -d not being valid with save-parser")
+      } (ExitCode.Usage)
+    }
+  }
 
-  // See DFDL-1141
-  /*@Test def test_3036_CLI_Saving_SaveParser_debug() {
+  // DAFFODIL-1141
+  /*@Test*/ def test_3037_CLI_Saving_SaveParser_trace(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
 
-    val cmd = Util.binPath + " -d save-parser -s daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd -r matrix savedParser.xml\n"
-    val shell = Util.start(cmd)
+    withTempFile { parser =>
+      runCLI(args"-t save-parser -s $schema -r matrix $parser") { cli =>
+        cli.expectErr("Some error about -t not being valid with save-parser")
+      } (ExitCode.Usage)
+    }
+  }
 
-    val cmd2 = Util.binPath + " parse --parser savedParser.xml daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt\n"
-    shell.send(cmd2)
-    shell.expect(contains("(debug)"))
-    shell.send("continue\n")
-    shell.send("quit\n")
+  @Test def test_3572_CLI_Saving_SaveParser_unparse(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input1.txt.xml")
 
-    shell.send("exit\n")
-    shell.expect(eof())
-    shell.close()
-  }
+    withTempFile { parser =>
+      runCLI(args"-t save-parser -s $schema -r matrix $parser") { cli =>
+      } (ExitCode.Success)
 
-  @Test def test_3037_CLI_Saving_SaveParser_trace() {
-
-    val cmd = Util.binPath + " -t save-parser -s daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_15.dfdl.xsd savedParser.xml\n"
-    val shell = Util.start(cmd)
-
-    val cmd2 = "echo test| " + Util.binPath + " parse --parser savedParser.xml\n"
-    shell.send(cmd2)
-    shell.expect(contains("parser: <Element name='rabbitHole'><ComplexType>...</ComplexType></Element name='rabbitHole'>"))
-
-    shell.send("exit\n")
-    shell.expect(eof())
-    shell.close()
-  }*/
-
-  /* // See DFDL-1342
-  @Test def test_3572_CLI_Saving_SaveParser_unparse() {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/output/output1.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-    val shell = Util.start("")
-
-    try {
-      val saveCmd = String.format("%s save-parser -s %s -r matrix %s", Util.binPath, testSchemaFile, savedParserFile.getName())
-      shell.sendLine(saveCmd)
-
-      val cmd = String.format("%s unparse --parser %s %s", Util.binPath, savedParserFile.getName(), testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("0,1,2"))
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
+      runCLI(args"unparse --parser $parser $input") { cli =>
+        cli.expect("0,1,2")
+      } (ExitCode.Success)
     }
   }
-*/
 
   @Test def test_3573_CLI_Saving_SaveParser_unparse2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input12.txt")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input12.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val saveCmd = String.format("%s save-parser -s %s -r e1 %s", Util.binPath, testSchemaFile, savedParserFile.getName())
-      shell.sendLine(saveCmd)
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r e1 $parser") { cli =>
+      } (ExitCode.Success)
 
-      val cmd = String.format("%s unparse --parser %s %s", Util.binPath, savedParserFile.getName(), testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("Hello"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
+      runCLI(args"unparse --parser $parser $input") { cli =>
+        cli.expect("Hello")
+      } (ExitCode.Success)
     }
   }
 
   @Test def test_3941_CLI_Saving_SaveParser_tunables(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input12.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val saveCmd = String.format("%s save-parser -s %s -r e1 -T parseUnparsePolicy=parseOnly %s", Util.binPath, testSchemaFile, savedParserFile.getName())
-      shell.sendLine(saveCmd)
-
-      val cmd = String.format("%s unparse --parser %s %s", Util.binPath, savedParserFile.getName(), testInputFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("[error]"))
-      shell.expectIn(1, contains("Runtime Schema Definition Error: This schema was compiled without unparse support."))
-
-      Util.expectExitCode(ExitCode.UnparseError, shell)
-      shell.sendLine("exit")
-      shell.expect(eof())
-    } finally {
-      shell.close()
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input12.txt")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r e1 -T parseUnparsePolicy=parseOnly $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"unparse --parser $parser $input") { cli =>
+        cli.expectErr("[error]")
+        cli.expectErr("Runtime Schema Definition Error: This schema was compiled without unparse support.")
+      } (ExitCode.UnparseError)
     }
   }
 
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestEmbedded.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestEmbedded.scala
index eb8d3701e..c2bd6ee9b 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestEmbedded.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestEmbedded.scala
@@ -17,92 +17,122 @@
 
 package org.apache.daffodil.schematron
 
-import net.sf.expectit.matcher.Matchers.sequence
-import org.apache.daffodil.Main.ExitCode
+import java.util.UUID
+
 import org.junit.Test
 
-import java.util.UUID
+import org.apache.daffodil.CLI.Util._
+import org.apache.daffodil.Main.ExitCode
 
 class TestEmbedded {
-  @Test def alwaysFails(): Unit = withShell(ExitCode.ParseError) {
-    val data = mktmp(UUID.randomUUID.toString)
-    val schema = "xsd/always-fails-1.dfdl.xsd"
-    s"parse --validate schematron={{$schema}} -s {{$schema}} $data" -> lineEndsWith("</always-fails>")
+  @Test def alwaysFails(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/always-fails-1.dfdl.xsd")
+    
+    runCLI(args"""parse --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.sendLine(UUID.randomUUID.toString, inputDone = true)
+      cli.expect("</always-fails>")
+    } (ExitCode.ParseError)
   }
 
-  @Test def unitPriceWithoutValidation(): Unit = withShell(ExitCode.Success) {
-    val data = mktmp("widget,monday,1,$5.00,$5.00")
-    val schema = "xsd/unit_price.dfdl.xsd"
-    s"parse -r list -s {{$schema}} $data" -> lineEndsWith("</ex:list>")
+  @Test def unitPriceWithoutValidation(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/unit_price.dfdl.xsd")
+
+    runCLI(args"""parse -r list -s $schema""") { cli =>
+      cli.send("widget,monday,1,$5.00,$5.00", inputDone = true)
+      cli.expect("</ex:list>")
+    } (ExitCode.Success)
   }
 
-  @Test def unitPriceWithValidation(): Unit = withShell(ExitCode.ParseError, JoinStdError) {
-    val data = mktmp("widget,monday,1,$5.00,$6.00")
-    val schema = "xsd/unit_price.dfdl.xsd"
-    s"parse -r list --validate schematron={{$schema}} -s {{$schema}} $data" -> sequence(
-      lineEndsWith("</ex:list>"),
-      anyLines(3)
-    )
+  @Test def unitPriceWithValidation(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/unit_price.dfdl.xsd")
+
+    runCLI(args"""parse -r list --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.send("widget,monday,1,$5.00,$6.00", inputDone = true)
+      cli.expect("</ex:list>")
+      cli.expectErr("Validation Error: wrong unit price for widget, monday")
+    } (ExitCode.ParseError)
   }
 
-  @Test def unitPriceWithValidationCheckMessage(): Unit = withShell(ExitCode.ParseError, JoinStdError) {
-    val data = mktmp("widget,monday,5,$5.00,$25.00||gadget,tuesday,1,$10.00,$11.00")
-    val schema = "xsd/unit_price.dfdl.xsd"
-    s"parse -r list --validate schematron={{$schema}} -s {{$schema}} $data" -> sequence(
-      lineEndsWith("</ex:list>"),
-      lineEndsWith("[error] Validation Error: wrong unit price for gadget, tuesday"),
-      anyLines(2)
-    )
+  @Test def unitPriceWithValidationCheckMessage(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/unit_price.dfdl.xsd")
+
+    runCLI(args"""parse -r list --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.send("widget,monday,5,$5.00,$25.00||gadget,tuesday,1,$10.00,$11.00", inputDone = true)
+      cli.expect("</ex:list>")
+      cli.expectErr("Validation Error: wrong unit price for gadget, tuesday")
+    } (ExitCode.ParseError)
   }
 
-  @Test def extends1(): Unit = withShell(ExitCode.Success) {
-    val data = mktmp("bob;l;smith")
-    val schema = "xsd/extends-1.dfdl.xsd"
-    s"parse --validate schematron={{$schema}} -s {{$schema}} $data" -> lineEndsWith("</name>")
+  @Test def extends1(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/extends-1.dfdl.xsd")
+
+    runCLI(args"""parse --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.send("bob;l;smith", inputDone = true)
+      cli.expect("</name>")
+    } (ExitCode.Success) 
   }
 
-  @Test def extends2(): Unit = withShell(ExitCode.Success) {
-    val data = mktmp("ob;;smith")
-    val schema = "xsd/extends-1.dfdl.xsd"
-    s"parse --validate schematron={{$schema}} -s {{$schema}} $data" -> lineEndsWith("</name>")
+  @Test def extends2(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/extends-1.dfdl.xsd")
+
+    runCLI(args"""parse --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.send("ob;;smith", inputDone = true)
+      cli.expect("</name>")
+    } (ExitCode.Success) 
   }
 
-  @Test def extends3(): Unit = withShell(ExitCode.ParseError, JoinStdError) {
-    val data = mktmp(";;smith")
-    val schema = "xsd/extends-1.dfdl.xsd"
-    s"parse --validate schematron={{$schema}} -s {{$schema}} $data" -> validationError("first is blank")
+  @Test def extends3(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/extends-1.dfdl.xsd")
+
+    runCLI(args"""parse --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.send(";;smith", inputDone = true)
+      cli.expectErr("Validation Error: first is blank")
+    } (ExitCode.ParseError)
   }
 
-  @Test def extends4(): Unit = withShell(ExitCode.ParseError, JoinStdError) {
-    val data = mktmp("bob;l;")
-    val schema = "xsd/extends-1.dfdl.xsd"
-    s"parse --validate schematron={{$schema}} -s {{$schema}} $data" -> validationError("last is blank")
+  @Test def extends4(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/extends-1.dfdl.xsd")
+
+    runCLI(args"""parse --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.send("bob;l;", inputDone = true)
+      cli.expectErr("Validation Error: last is blank")
+    } (ExitCode.ParseError)
   }
 
-  @Test def extends5(): Unit = withShell(ExitCode.ParseError, JoinStdError) {
-    val data = mktmp(";l;")
-    val schema = "xsd/extends-1.dfdl.xsd"
-    s"parse --validate schematron={{$schema}} -s {{$schema}} $data" -> sequence(
-      validationError("last is blank"),
-      validationError("first is blank")
-    )
+  @Test def extends5(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/extends-1.dfdl.xsd")
+
+    runCLI(args"""parse --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.send(";l;", inputDone = true)
+      cli.expectErr("Validation Error: last is blank")
+      cli.expectErr("Validation Error: first is blank")
+    } (ExitCode.ParseError)
   }
 
-  @Test def testWithNs1(): Unit = withShell(ExitCode.Success) {
-    val data = mktmp("0;1")
-    val schema = "xsd/with-ns-1.dfdl.xsd"
-    s"parse --validate schematron={{$schema}} -s {{$schema}} $data" -> lineEndsWith("</myns:interval>")
+  @Test def testWithNs1(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/with-ns-1.dfdl.xsd")
+
+    runCLI(args"""parse --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.send("0;1", inputDone = true)
+      cli.expect("</myns:interval>")
+    } (ExitCode.Success)
   }
 
-  @Test def testWithNs2(): Unit = withShell(ExitCode.ParseError, JoinStdError) {
-    val data = mktmp("2;1")
-    val schema = "xsd/with-ns-1.dfdl.xsd"
-    s"parse --validate schematron={{$schema}} -s {{$schema}} $data" -> validationError()
+  @Test def testWithNs2(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/with-ns-1.dfdl.xsd")
+
+    runCLI(args"""parse --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.send("2;1", inputDone = true)
+      cli.expectErr("Validation Error")
+    } (ExitCode.ParseError)
   }
 
-  @Test def testWithNs3(): Unit = withShell(ExitCode.ParseError, JoinStdError) {
-    val data = mktmp("0;0")
-    val schema = "xsd/with-ns-1.dfdl.xsd"
-    s"parse --validate schematron={{$schema}} -s {{$schema}} $data" -> validationError()
+  @Test def testWithNs3(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/with-ns-1.dfdl.xsd")
+
+    runCLI(args"""parse --validate schematron="${jsonEscape(schema.toString)}" -s $schema""") { cli =>
+      cli.send("0;0", inputDone = true)
+      cli.expectErr("Validation Error")
+    } (ExitCode.ParseError)
   }
 }
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestSvrlOutput.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestSvrlOutput.scala
index 879e03a43..d68e1bb38 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestSvrlOutput.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestSvrlOutput.scala
@@ -17,138 +17,152 @@
 
 package org.apache.daffodil.schematron
 
-import net.sf.expectit.matcher.Matchers.sequence
-import org.apache.daffodil.CLI.Util
-import org.apache.daffodil.Main.ExitCode
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardOpenOption.APPEND
+import java.nio.charset.StandardCharsets.UTF_8
+
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
 import org.junit.Test
 
-import java.nio.file.Path
-import java.nio.file.Paths
 import scala.xml.XML
 
+import org.apache.daffodil.CLI.Util._
+import org.apache.daffodil.Main.ExitCode
+
 class TestSvrlOutput {
-  import TestValidating._
+
+  private def makeConf(conf: Path, schematron: Path, svrl: Path): Unit = {
+    Files.write(conf, s"""schematron.path="${jsonEscape(schematron.toString)}"\n""".getBytes(UTF_8), APPEND)
+    Files.write(conf, s"""schematron.svrl.file="${jsonEscape(svrl.toString)}"\n""".getBytes(UTF_8), APPEND)
+  }
 
   @Test def validationSuccess(): Unit = {
-    val svrlPath = makeTempFilePath()
-    val confFile = mkTmpConf(never, svrlPath)
-    withShell(ExitCode.Success, stderr=true) {
-      s"parse --validate schematron=$confFile -s {{$uuid}} {$data}" -> alwaysResult
-    }
-
-    val svrlFile = svrlPath.toFile
-    assertTrue(svrlFile.exists())
-
-    try {
-      XML.loadFile(svrlFile) match {
-        case <svrl:schematron-output>{rules @ _*}</svrl:schematron-output> =>
-          val res = rules.find {
-            case <svrl:failed-assert>{  _* }</svrl:failed-assert> => true
-            case _ => false
-          }
-          // we should not have found failures
-          assertFalse(res.isDefined)
-        case _ =>
-          fail("schematron pattern didnt match")
+    val schema = path("daffodil-schematron/src/test/resources/xsd/string.dfdl.xsd")
+    val schematron = path("daffodil-schematron/src/test/resources/sch/never-fails.sch")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/uuid.txt")
+
+    withTempFile(".conf", { conf =>
+      withTempFile { svrl =>
+
+        makeConf(conf, schematron, svrl)
+
+        runCLI(args"parse --validate schematron=$conf -s $schema $input") { cli =>
+          cli.expect("<never-fails>2f6481e6-542c-11eb-ae93-0242ac130002</never-fails>")
+        } (ExitCode.Success)
+
+        XML.loadFile(svrl.toFile) match {
+          case <svrl:schematron-output>{rules @ _*}</svrl:schematron-output> =>
+            val res = rules.find {
+              case <svrl:failed-assert>{  _* }</svrl:failed-assert> => true
+              case _ => false
+            }
+            // we should not have found failures
+            assertFalse(res.isDefined)
+          case _ =>
+            fail("schematron pattern didnt match")
+        }
       }
-    } finally {
-      svrlFile.delete()
-    }
+    })
   }
 
   // should get validation output file on a validation failure
   @Test def validationFailure(): Unit = {
-    val svrlPath = makeTempFilePath()
-    val confFile = mkTmpConf(always, svrlPath)
-    withShell(ExitCode.ParseError) {
-      s"parse --validate schematron=$confFile -s {{$uuid}} {$data}" -> alwaysResult
-    }
-
-    val svrlFile = svrlPath.toFile
-    assertTrue(svrlFile.exists())
-
-    try {
-      XML.loadFile(svrlFile) match {
-        case <svrl:schematron-output>{rules @ _*}</svrl:schematron-output> =>
-          val res = rules.find {
-            case <svrl:failed-assert>{  _* }</svrl:failed-assert> => true
-            case _ => false
-          }
-          // we should have found some failures
-          assertTrue(res.isDefined)
-        case _ =>
-          fail("schematron pattern didnt match")
+    val schema = path("daffodil-schematron/src/test/resources/xsd/string.dfdl.xsd")
+    val schematron = path("daffodil-schematron/src/test/resources/sch/always-fails.sch")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/uuid.txt")
+
+    withTempFile(".conf", { conf =>
+      withTempFile { svrl =>
+
+        makeConf(conf, schematron, svrl)
+
+        runCLI(args"parse --validate schematron=$conf -s $schema $input") { cli =>
+          cli.expect("<never-fails>2f6481e6-542c-11eb-ae93-0242ac130002</never-fails>")
+        } (ExitCode.ParseError)
+
+        XML.loadFile(svrl.toFile) match {
+          case <svrl:schematron-output>{rules @ _*}</svrl:schematron-output> =>
+            val res = rules.find {
+              case <svrl:failed-assert>{  _* }</svrl:failed-assert> => true
+              case _ => false
+            }
+            // we should have found some failures
+            assertTrue(res.isDefined)
+          case _ =>
+            fail("schematron pattern didnt match")
+        }
       }
-    } finally {
-      svrlFile.delete()
-    }
+    })
   }
 
   // shouldnt get a validation output file on parse failure
   // based on negative test test_996_CLI_Parsing_negativeTest04
   @Test def parseFailure(): Unit = {
-    val svrlPath = makeTempFilePath()
-    val confFile = mkTmpConf(never, svrlPath)
-    val data = mktmp("12")
-    withShell(ExitCode.UnableToCreateProcessor, stderr = true) {
-      val schemaFile = Util.daffodilPath(
-        "daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-      val schema = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-      s"parse --validate schematron=$confFile -s $schema -r unknown $data" ->
-        lineEndsWith("No root element found for unknown in any available namespace")
-    }
-
-    val svrlFile = svrlPath.toFile
-    if (svrlFile.exists()) {
-      svrlFile.delete()
-      fail("svrl file should not exist on failed parse")
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val schematron = path("daffodil-schematron/src/test/resources/sch/never-fails.sch")
+
+    withTempFile(".conf", { conf =>
+      withTempFile { svrl =>
+
+        makeConf(conf, schematron, svrl)
+
+        runCLI(args"parse --validate schematron=$conf -s $schema -r unknown") { cli =>
+          cli.send("12", inputDone = true)
+          cli.expectErr("No root element found for unknown in any available namespace")
+        } (ExitCode.UnableToCreateProcessor)
+      }
+    })
   }
 
   // parse should fail with validation diagnostic when unable to write to the specified location
   @Test def outputPathFailure(): Unit = {
-    val badSvrlPath = Paths.get("thisisnotavalidlocation/schematron.svrl")
-    val confFile = mkTmpConf(never, badSvrlPath)
-    withShell(ExitCode.ParseError, JoinStdError) {
-      s"""parse --validate schematron="$confFile" -s {{$uuid}} {$data}""" -> sequence(
-        lineEndsWithRegex(s"\\[error] Validation Error: .+"),
-        anyLines(2))
-    }
-
-    val svrlFile = badSvrlPath.toFile
-    assertFalse(svrlFile.exists())
+    val schema = path("daffodil-schematron/src/test/resources/xsd/string.dfdl.xsd")
+    val schematron = path("daffodil-schematron/src/test/resources/sch/never-fails.sch")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/uuid.txt")
+    val svrl = path("thisisnotavalidlocation/schematron.svrl")
+
+    withTempFile(".conf", { conf =>
+
+      makeConf(conf, schematron, svrl)
+
+      runCLI(args"parse --validate schematron=$conf -s $schema $input") { cli =>
+        cli.expectErr("[error] Validation Error")
+      } (ExitCode.ParseError)
+    })
   }
 
   // validator output should overwrite existing file
   @Test def overwriteExistingFile(): Unit = {
-    val svrlPath = mktmp("=== this content will be overwritten ===")
-    val confFile = mkTmpConf(never, svrlPath)
-    withShell(ExitCode.Success) {
-      s"parse --validate schematron=$confFile -s {{$uuid}} {$data}" -> alwaysResult
-    }
-
-    val svrlFile = svrlPath.toFile
-    assertTrue(svrlFile.exists())
-
-    try {
-      XML.loadFile(svrlFile) match {
-        case <svrl:schematron-output>{rules @ _*}</svrl:schematron-output> =>
-          val res = rules.find {
-            case <svrl:failed-assert>{  _* }</svrl:failed-assert> => true
-            case _ => false
-          }
-          // we should not have found failures
-          assertFalse(res.isDefined)
-        case _ =>
-          fail("schematron pattern didnt match")
+    val schema = path("daffodil-schematron/src/test/resources/xsd/string.dfdl.xsd")
+    val schematron = path("daffodil-schematron/src/test/resources/sch/never-fails.sch")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/uuid.txt")
+
+    withTempFile(".conf", { conf =>
+      withTempFile { svrl =>
+        Files.write(svrl, "=== this content will be overwritten ===".getBytes(UTF_8), APPEND)
+
+        makeConf(conf, schematron, svrl)
+
+        runCLI(args"parse --validate schematron=$conf -s $schema $input") { cli =>
+          cli.expect("<never-fails>2f6481e6-542c-11eb-ae93-0242ac130002</never-fails>")
+        } (ExitCode.Success)
+
+        XML.loadFile(svrl.toFile) match {
+          case <svrl:schematron-output>{rules @ _*}</svrl:schematron-output> =>
+            val res = rules.find {
+              case <svrl:failed-assert>{  _* }</svrl:failed-assert> => true
+              case _ => false
+            }
+            // we should not have found failures
+            assertFalse(res.isDefined)
+          case _ =>
+            fail("schematron pattern didnt match")
+        }
       }
-    } finally {
-      svrlFile.delete()
-    }
+    })
   }
 
-  private def makeTempFilePath(): Path = Paths.get(System.getProperty("java.io.tmpdir"), "schTestRaw.svrl")
 }
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestValidating.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestValidating.scala
index 0ba54f777..4119eef0e 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestValidating.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/TestValidating.scala
@@ -17,32 +17,43 @@
 
 package org.apache.daffodil.schematron
 
-import org.apache.daffodil.Main.ExitCode
 import org.junit.Test
 
-object TestValidating {
-  val data = "input/uuid.txt"
-  val uuid = "xsd/string.dfdl.xsd"
-  val never = "sch/never-fails.sch"
-  val always = "sch/always-fails.sch"
+import org.apache.daffodil.CLI.Util._
+import org.apache.daffodil.Main.ExitCode
 
-  val alwaysResult = regexLine("<.+-fails>2f6481e6-542c-11eb-ae93-0242ac130002</.+-fails>")
-}
 class TestValidating {
-  import TestValidating._
 
   // always fails sch, but no validate flag so it should pass
-  @Test def nonShouldPass(): Unit = withShell(ExitCode.Success) {
-    s"parse -s {{$uuid}} {$data}" -> alwaysResult
+  @Test def nonShouldPass(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/string.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/uuid.txt")
+
+    runCLI(args"parse -s $schema $input") { cli =>
+      cli.expect("<never-fails>2f6481e6-542c-11eb-ae93-0242ac130002</never-fails>")
+    } (ExitCode.Success)
   }
 
   // always fails sch, with validate flag should fail
-  @Test def failShouldFail(): Unit = withShell(ExitCode.ParseError) {
-    s"parse --validate schematron={{$always}} -s {{$uuid}} {$data}" -> alwaysResult
+  @Test def failShouldFail(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/string.dfdl.xsd")
+    val schematron = path("daffodil-schematron/src/test/resources/sch/always-fails.sch")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/uuid.txt")
+
+    runCLI(args"""parse --validate schematron="${jsonEscape(schematron.toString)}" -s $schema $input""") { cli =>
+      cli.expect("<never-fails>2f6481e6-542c-11eb-ae93-0242ac130002</never-fails>")
+      cli.expectErr("[error] Validation Error: never fails")
+    } (ExitCode.ParseError)
   }
 
   // never fails sch, with validate flag should pass
-  @Test def passShouldPass(): Unit = withShell(ExitCode.Success) {
-    s"parse --validate schematron={{$never}} -s {{$uuid}} {$data}" -> alwaysResult
+  @Test def passShouldPass(): Unit = {
+    val schema = path("daffodil-schematron/src/test/resources/xsd/string.dfdl.xsd")
+    val schematron = path("daffodil-schematron/src/test/resources/sch/never-fails.sch")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/uuid.txt")
+
+    runCLI(args"""parse --validate schematron="${jsonEscape(schematron.toString)}" -s $schema $input""") { cli =>
+      cli.expect("<never-fails>2f6481e6-542c-11eb-ae93-0242ac130002</never-fails>")
+    } (ExitCode.Success)
   }
 }
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/package.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/package.scala
deleted file mode 100644
index 68ad68118..000000000
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/schematron/package.scala
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.daffodil
-
-import net.sf.expectit.MultiResult
-import org.apache.daffodil.CLI.Util
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.eof
-import net.sf.expectit.matcher.Matchers.regexp
-import net.sf.expectit.matcher.Matchers.sequence
-import net.sf.expectit.Result
-import net.sf.expectit.matcher.Matcher
-import org.apache.daffodil.Main.ExitCode
-
-import java.io.File
-import java.io.FileOutputStream
-import java.nio.file.Path
-import java.nio.file.Paths
-import scala.util.matching.Regex
-
-/**
- * Reference implementation for enhancements called for in DAFFODIL-2381, implemented specifically for Schematron.
- *
- * There are a number of bits in here that need abstracted to apply it across all CLI tests but it demonstrates one way
- * to roll up the repetative operations required in the CLI tests.
- *
- * One less obvious schematron specific bit in these tests is the path resolution in the command line using mustache
- * brackets.  There is a difference between where single and double brackets base the root of their paths from.  This
- * is documented below in more detail.
- *
- */
-package object schematron {
-  val FailureErrorCode = 1
-  val JoinStdError = true
-
-  def resolvePath(argstring: String): String =
-    mustache.replaceAllIn(argstring, _ match {
-      case mustache2(p) => schPath(p)
-      case mustache1(p) => cliPath(p)
-    })
-
-  /**
-   * executes a command in a shell with the provided expectations and error code using a mustache syntax looks up files
-   * from local resources {path} or the daffodil-schematron resources {{path}}
-   * @param ec expected error code
-   * @param stderr join stderr in output
-   * @param body 2 tuple of daffodil arguments and expectation
-   */
-  def withShell[R <: Result](ec: ExitCode.Value, stderr: Boolean = false)(body: => (String,  Matcher[R])): Unit = {
-    val (argstring, expectation) = body
-    val args = resolvePath(argstring)
-
-    val joinStdErr = if(stderr) "2>&1" else ""
-    val cmd = Util.binPath :: args :: joinStdErr :: Nil mkString " "
-    val shell = Util.start("")
-    try {
-      shell.sendLine(cmd).expect(expectation)
-
-      Util.expectExitCode(ec, shell)
-
-      shell.sendLine("exit")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
-  }
-
-  // two reasons for this bit of parsing indirection
-  // 1. support consuming resources from multiple projects
-  // 2. avoid doing the noisy and repetetive resolution in the unit tests
-  private val mustache = """\{{1,2}(.+?)}{1,2}""".r.unanchored
-  private val mustache1 = """\{(.+?)}""".r.unanchored
-  private val mustache2 = """\{\{(.+?)}}""".r.unanchored
-
-  // have to use platform specific matching here as the $ doesnt match on Windows
-  // potentially because of the withInputFilters set on the shell in Util
-
-
-  private def schPath(p: String): String = fixpath(s"daffodil-schematron/src/test/resources/$p")
-  private def cliPath(p: String): String = fixpath(s"daffodil-cli/src/it/resources/org/apache/daffodil/CLI/$p")
-  private def fixpath(p: String): String = {
-    val full = Paths.get(Util.dafRoot, p).toString
-    val argfix = full.replaceAll("""\\""", "/")
-    Regex.quoteReplacement(argfix)
-  }
-
-  /**
-   * number of lines, any content on those lines
-   * @param n line count
-   * @return
-   */
-  def anyLines(n: Int): Matcher[_] = regexp(Seq.fill(n)(s".+$eol").mkString)
-
-  /**
-   * make a temp file containing the bytes
-   * @param str
-   * @return
-   */
-  def mktmp(d: Array[Byte], prefix: String = "schval", suffix: String = "data"): Path = {
-    val f = File.createTempFile(prefix, suffix)
-    f.deleteOnExit()
-    val os = new FileOutputStream(f)
-    os.write(d)
-    os.close()
-    f.toPath
-  }
-  def mktmp(str: String): Path = mktmp(str.getBytes)
-
-  def mkTmpConf(schPath: String, svrlPath: Path): String = {
-    val svrl = Regex.quoteReplacement(svrlPath.toString.replaceAll("""\\""", "/"))
-    val sch = resolvePath(s"{{$schPath}}")
-    mktmp(
-      s"""schematron.path="$sch"
-         |schematron.svrl.file="$svrl"
-         |""".stripMargin.getBytes, suffix = ".conf").toString
-  }
-
-  /**
-   * the common pattern on stderr when a validation error is hit
-   * @param txt optional additional validation error text
-   * @return matcher
-   */
-  def validationError(txt: String = ""): Matcher[MultiResult] =
-    sequence(contains(s"[error] Validation Error: $txt"), anyLines(3))
-
-  private lazy val eol = "\n"
-  def lineEndsWith(txt: String): Matcher[Result] = contains(s"$txt$eol")
-  def lineEndsWithRegex(pattern: String): Matcher[Result] = regexp(s"$pattern$eol")
-
-  def regexLine(pattern: String): Matcher[Result] = regexp(s"$pattern$eol")
-}
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/tdml/TestCLItdml.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/tdml/TestCLItdml.scala
new file mode 100644
index 000000000..75a442036
--- /dev/null
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/tdml/TestCLItdml.scala
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.tdml
+
+import org.junit.Test
+
+import org.apache.daffodil.CLI.Util._
+import org.apache.daffodil.Main.ExitCode
+
+class TestCLItdml {
+
+  @Test def test_995_CLI_Tdml_Listing_negativeTest01(): Unit = {
+    val tdml = path("daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml")
+
+    runCLI(args"test $tdml escape_entry1 escape_entry2-11 escape_entry1-5 escape_entry4_3") { cli =>
+      cli.expect("Total: 4, Pass: 2, Fail: 0, Not Found: 2")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1001_CLI_Tdml_Listing_execRegex01(): Unit = {
+    val tdml = path("daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml")
+
+    runCLI(args"test --regex $tdml escape_entry4_\d") { cli =>
+      cli.expect("Total: 9, Pass: 9, Fail: 0, Not Found: 0")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1000_CLI_Tdml_Listing_listRegex02(): Unit = {
+    val tdml = path("daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml")
+
+    runCLI(args"test -l --regex $tdml escape_entryb-\d+") { cli =>
+    } (ExitCode.Success)
+  }
+
+  @Test def test_999_CLI_Tdml_Listing_listRegex01(): Unit = {
+    val tdml = path("daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml")
+
+    runCLI(args"test -l --regex $tdml escape_entry4_\d+") { cli =>
+      cli.expect("escape_entry4_1")
+      cli.expect("escape_entry4_10")
+      cli.expect("escape_entry4_20")
+      cli.expect("escape_entry4_9")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_994_CLI_Tdml_Listing_execAll(): Unit = {
+    val tdml = path("daffodil-test/src/test/resources/org/apache/daffodil/section31/escape_characters/Escapes.tdml")
+
+    runCLI(args"test $tdml") { cli =>
+      cli.expect("Total: 88, Pass: 88, Fail: 0, Not Found: 0")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_993_CLI_Tdml_Listing_listAll(): Unit = {
+    val tdml = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/Entities.tdml")
+
+    runCLI(args"test -l $tdml") { cli =>
+      cli.expect("byte_entities_6_01")
+      cli.expect("dataDumpEncoding")
+      cli.expect("emptyStringEntityTermInExpression_01")
+      cli.expect("text_entities_6_04")
+      cli.expect("whitespace_10")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_992_CLI_Tdml_Listing_singleTestList(): Unit = {
+    val tdml = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/Entities.tdml")
+
+    runCLI(args"test -l $tdml byte_entities_6_08") { cli =>
+      cli.expect("byte_entities_6_08")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_990_CLI_Tdml_Listing_singleTest(): Unit = {
+    val tdml = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/Entities.tdml")
+
+    runCLI(args"test $tdml byte_entities_6_08") { cli =>
+      cli.expect("[Pass] byte_entities_6_08")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_catch_TestNotCompatible(): Unit = {
+    val tdml = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/testNonCompatibleImplementation.tdml")
+
+    runCLI(args"test -iii $tdml testNotCompatibleImplementation1") { cli =>
+      cli.expect("[Skipped] testNotCompatibleImplementation1 (not compatible with implementation: daffodil)")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_catch_TestBadArguments(): Unit = {
+    val tdml = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/testNonCompatibleImplementation.tdml")
+
+    runCLI(args"test -I notDaffodilC $tdml") { cli =>
+      cli.expectErr("[error] Bad arguments for option 'implementation'")
+    } (ExitCode.Usage)
+  }
+
+  @Test def test_CLI_Tdml_implementation(): Unit = {
+    val tdml = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/testNonCompatibleImplementation.tdml")
+
+    runCLI(args"test -I daffodilC $tdml testDaffodilCImplementation1") { cli =>
+      cli.expect("[Pass] testDaffodilCImplementation1")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1016_CLI_Tdml_Listing_listVerbose(): Unit = {
+    val tdml = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/assertions/assert.tdml")
+
+    runCLI(args"test -l -i --regex $tdml assertPattern.*") { cli =>
+      cli.expect("assertPatternAndExp              s2                e3         Section 7 - Assert Schema Error for Expression/Pattern - DFDL-7-047R")
+    } (ExitCode.Success)
+  }
+}
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/tunables/TestCLITunables.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/tunables/TestCLITunables.scala
index 83689e73d..47076d7c3 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/tunables/TestCLITunables.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/tunables/TestCLITunables.scala
@@ -18,240 +18,182 @@
 package org.apache.daffodil.tunables
 
 import org.junit.Test
-import org.apache.daffodil.CLI.Util
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.eof
+
+import org.apache.daffodil.CLI.Util._
 import org.apache.daffodil.Main.ExitCode
 
 class TestCLITunables {
 
-  val unqualifiedPathStep01 = Util.getExpectedString("unqualified_path_step_01.txt")
-  val unqualifiedPathStep02 = Util.getExpectedString("unqualified_path_step_02.txt")
-  val unqualifiedPathStep03 = Util.getExpectedString("unqualified_path_step_03.txt")
-  val unqualifiedPathStep04 = Util.getExpectedString("unqualified_path_step_04.txt")
-
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_noNamespace_test_01(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_01 -TunqualifiedPathStepPolicy=noNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(unqualifiedPathStep01))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_01 -TunqualifiedPathStepPolicy=noNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">1</s>""")
+      cli.expect("</test_01>")
+    } (ExitCode.Success)
   }
 
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_noNamespace_test_02(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_02 -TunqualifiedPathStepPolicy=noNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      //shell.expect(contains(unqualifiedPathStep02))
-      shell.expectIn(1, contains("Schema Definition Error"))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_02 -TunqualifiedPathStepPolicy=noNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expectErr("Schema Definition Error")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_noNamespace_test_03(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_03 -TunqualifiedPathStepPolicy=noNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      //shell.expect(contains(unqualifiedPathStep03))
-      shell.expectIn(1, contains("Schema Definition Error"))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_03 -TunqualifiedPathStepPolicy=noNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expectErr("Schema Definition Error")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_noNamespace_test_04(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_04 -TunqualifiedPathStepPolicy=noNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(unqualifiedPathStep04))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_04 -TunqualifiedPathStepPolicy=noNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">2</s>""")
+      cli.expect("</test_04>")
+    } (ExitCode.Success)
   }
 
-
-
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_defaultNamespace_test_01(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_01 -TunqualifiedPathStepPolicy=defaultNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(unqualifiedPathStep01))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_01 -TunqualifiedPathStepPolicy=defaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">1</s>""")
+      cli.expect("</test_01>")
+    } (ExitCode.Success)
   }
 
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_defaultNamespace_test_02(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_02 -TunqualifiedPathStepPolicy=defaultNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(unqualifiedPathStep02))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_02 -TunqualifiedPathStepPolicy=defaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">1</s>""")
+      cli.expect("</test_02>")
+    } (ExitCode.Success)
   }
 
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_defaultNamespace_test_03(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_03 -TunqualifiedPathStepPolicy=defaultNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      //shell.expect(contains(unqualifiedPathStep03))
-      shell.expectIn(1, contains("Schema Definition Error"))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
-  }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
 
-  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_defaultNamespace_test_04(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_04 -TunqualifiedPathStepPolicy=defaultNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("Schema Definition Error"))
-      //shell.expect(contains(unqualifiedPathStep04))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r test_03 -TunqualifiedPathStepPolicy=defaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expectErr("Schema Definition Error")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_defaultNamespace_test_04(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
 
+    runCLI(args"parse -s $schema -r test_04 -TunqualifiedPathStepPolicy=defaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expectErr("Schema Definition Error")
+    } (ExitCode.UnableToCreateProcessor)
+  }
 
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_preferDefaultNamespace_test_01(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_01 -TunqualifiedPathStepPolicy=preferDefaultNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(unqualifiedPathStep01))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_01 -TunqualifiedPathStepPolicy=preferDefaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">1</s>""")
+      cli.expect("</test_01>")
+    } (ExitCode.Success)
   }
 
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_preferDefaultNamespace_test_02(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_02 -TunqualifiedPathStepPolicy=preferDefaultNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(unqualifiedPathStep02))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_02 -TunqualifiedPathStepPolicy=preferDefaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">1</s>""")
+      cli.expect("</test_02>")
+    } (ExitCode.Success)
   }
 
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_preferDefaultNamespace_test_03(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_03 -TunqualifiedPathStepPolicy=preferDefaultNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      //shell.expect(contains(unqualifiedPathStep03))
-      shell.expectIn(1, contains("Schema Definition Error"))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_03 -TunqualifiedPathStepPolicy=preferDefaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expectErr("Schema Definition Error")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_preferDefaultNamespace_test_04(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("12") + "| %s parse -s %s -r test_04 -TunqualifiedPathStepPolicy=preferDefaultNamespace", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains(unqualifiedPathStep04))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_04 -TunqualifiedPathStepPolicy=preferDefaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">2</s>""")
+      cli.expect("</test_04>")
+    } (ExitCode.Success)
+  }
+
+  /**
+   * Suppresses SDW messages.
+   */
+  @Test def test_CLI_Parsing_SuppressSDEWarnings1(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/suppressWarnTest.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -TsuppressSchemaDefinitionWarnings=all") { cli =>
+      cli.send("a,b", inputDone = true)
+      cli.expect("<s1>a</s1>")
+      cli.expect("<s2>b</s2>")
+    } (ExitCode.Success)
+  }
+
+  /**
+   * Will display SDW warnings. Does not set the tunable that suppresses them.
+   */
+  @Test def test_CLI_Parsing_SuppressSDEWarnings2(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/suppressWarnTest.dfdl.xsd")
+
+    runCLI(args"parse -s $schema") { cli =>
+      cli.send("a,b", inputDone = true)
+      cli.expect("<s1>a</s1>")
+      cli.expect("<s2>b</s2>")
+      cli.expectErr("Schema Definition Warning")
+      cli.expectErr("dfdl:lengthKind")
+      cli.expectErr("delimited")
+      cli.expectErr("dfdl:length")
+    } (ExitCode.Success)
+  }
+
+  /**
+   * Saving the processor should compile and issue SDWs which we should see in
+   * the expected output. When reloading, we should NOT see a SDW because that
+   * isn't displayed on a reload of a compiled processor
+   */
+  @Test def test_CLI_Parsing_ReloadingDoesNotRepeatWarnings(): Unit = {
+    val schema = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/suppressWarnTest.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema $parser") { cli =>
+        cli.expectErr("Schema Definition Warning")
+      } (ExitCode.Success)
+
+      runCLI(args"parse -P $parser") { cli =>
+        cli.send("a,b", inputDone = true)
+        cli.expect("<s1>a</s1>")
+        cli.expect("<s2>b</s2>")
+      } (ExitCode.Success)
     }
   }
 
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/tunables/TestCLITunables2.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/tunables/TestCLITunables2.scala
deleted file mode 100644
index 5d36f81f5..000000000
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/tunables/TestCLITunables2.scala
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.daffodil.tunables
-
-import org.junit.Test
-import org.apache.daffodil.CLI.Util
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.eof
-import org.apache.daffodil.Main.ExitCode
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-
-class TestCLITunables2 {
-
-  val stdout = 0
-  val stderr = 1
-
-  /**
-   * Suppresses SDW messages.
-   */
-  @Test def test_CLI_Parsing_SuppressSDEWarnings1(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/suppressWarnTest.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      // note: 2>&1 is shell-speak for "connect stderr into stdout"
-      val cmd = String.format("""echo a,b| %s parse -s %s -TsuppressSchemaDefinitionWarnings=all 2>&1""", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("""<ex:e1 xmlns:ex="http://example.com">
-  <s1>a</s1>
-  <s2>b
-</s2>
-</ex:e1>""".replace("\r\n", "\n")))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
-  }
-
-  /**
-   * Will display SDW warnings. Does not set the tunable that suppresses them.
-   */
-  @Test def test_CLI_Parsing_SuppressSDEWarnings2(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/suppressWarnTest.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val shell = Util.start("")
-
-    try {
-      // note: 2>&1 is shell-speak for "connect stderr into stdout"
-      val cmd = String.format("""echo a,b| %s parse -s %s 2>&1""", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("""Schema Definition Warning"""))
-      shell.expect(contains("""dfdl:lengthKind"""))
-      shell.expect(contains("""delimited"""))
-      shell.expect(contains("""dfdl:length"""))
-      shell.expect(contains("""<ex:e1 xmlns:ex="http://example.com">
-  <s1>a</s1>
-  <s2>b
-</s2>
-</ex:e1>""".replace("\r\n", "\n")))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.sendLine("exit")
-      shell.expect(eof)
-    } finally {
-      shell.close()
-    }
-  }
-
-  @Test def test_CLI_Parsing_ReloadingDoesNotRepeatWarnings(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/suppressWarnTest.dfdl.xsd")
-    val testSchemaFile = if (Util.isWindows) Util.cmdConvert(schemaFile) else schemaFile
-    val compiledProcFile = Util.newTempFile("savedProc", ".bin")
-    compiledProcFile.deleteOnExit()
-    val compiledProcFilePath = compiledProcFile.getAbsolutePath()
-    //
-    // Scala compiler was having trouble with local blocks to provide
-    // separate scopes that have their own val shell = ....
-    //
-    // So I split this into two little local functions each of which knows
-    // about the same compiledProcFilePath.
-    //
-    def saveIt(): Unit = {
-      val shell = Util.start("")
-      try {
-        val cmd = String.format("""%s save-parser -s %s %s """, Util.binPath, testSchemaFile, compiledProcFilePath)
-        shell.sendLine(cmd)
-        //
-        // Saving the processor should compile and issue SDWs which we should see
-        // in the expected output
-        //
-        shell.expectIn(stderr, contains("""Schema Definition Warning"""))
-        Util.expectExitCode(ExitCode.Success, shell)
-        shell.sendLine("exit")
-        shell.expectIn(stdout, eof)
-      } finally {
-        shell.close()
-      }
-    }
-    def reloadIt(): Unit = {
-        val shell = Util.start("")
-        try {
-          val cmd = String.format("""echo a,b| %s parse -P %s """, Util.binPath, compiledProcFilePath)
-          shell.sendLine(cmd)
-          shell.sendLine("exit")
-          val output = shell.expectIn(stdout, eof).getBefore
-          val errout = shell.expectIn(stderr, eof).getBefore
-          //
-          // Let's make sure we get a parse result
-          //
-          assertTrue(output.contains("""<ex:e1 xmlns:ex="http://example.com">"""))
-          assertTrue(output.contains("""</ex:e1>"""))
-          //
-          // We should NOT see a SDW because that isn't displayed on a reload of a compiled processor
-          //
-          assertFalse(errout.contains("Warning"))
-          assertFalse(output.contains("Warning")) // in case it is somehow routed to stdout instead.
-        } finally {
-          shell.close()
-        }
-      }
-
-    saveIt()
-    reloadIt()
-  }
-}
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/udf/TestCLIUdfs.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/udf/TestCLIUdfs.scala
index 448190a0c..2898a2b1f 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/udf/TestCLIUdfs.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/udf/TestCLIUdfs.scala
@@ -17,37 +17,25 @@
 
 package org.apache.daffodil.udf
 
+import java.nio.file.Path
+
 import org.junit.Test
-import org.apache.daffodil.CLI.Util
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.anyOf
-import net.sf.expectit.matcher.Matchers.allOf
-import net.sf.expectit.matcher.Matchers.eof
-import org.apache.daffodil.Main.ExitCode
 
-import java.nio.file.FileSystems
-import java.nio.file.Files
-import scala.collection.JavaConverters._
+import org.apache.daffodil.CLI.Util._
+import org.apache.daffodil.Main.ExitCode
 
 class TestCLIUdfs {
 
-  lazy val testUdfsPaths = {
-    val dir = FileSystems.getDefault.getPath(Util.daffodilPath("daffodil-udf/target"))
-    val allClassFiles = Files.walk(dir).iterator.asScala
-      .filter { f => Files.isRegularFile(f) && f.toString.endsWith("class") }
-    val commonPrefix = allClassFiles
-      .flatMap {
-        fp =>
-          val filePath = fp.toString
-          val indexOfOrg = filePath.indexOfSlice("org")
-          if (indexOfOrg >= 0) {
-            // use index - 1 to remove trailing slash, which can cause
-            // accidental escaping on windows
-            val prefix = filePath.splitAt(indexOfOrg - 1)._1
-            Some(prefix)
-          } else None
-      }.toList.distinct
-    commonPrefix
+  /**
+   * Return a sequence of paths, made up of the one classpath containing all
+   * compiled UDF .class files, and any additional classpaths needed for a
+   * specific test. In most cases, the extra should just include the path to
+   * the directory containing the META-INF dir needed for the test.
+   */
+  private def udfClasspath(extra: String*): Seq[Path] = {
+    val classes = path("daffodil-udf/target/scala-2.12/test-classes/")
+    val paths = extra.map(path(_))
+    classes +: paths
   }
 
   /**
@@ -55,28 +43,15 @@ class TestCLIUdfs {
    * no User Defined Function calls, so they don't get loaded
    */
   @Test def test_noUdfsLoaded_regular_schema(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r fn_func", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(
-        allOf(
-          contains("<fn_func>"),
-          contains("<data>strng</data>"),
-          contains("<value>Hello,strng</value>"),
-          contains("</fn_func>")))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+
+    runCLI(args"-v parse -s $schema -r fn_func") { cli =>
+      cli.send("strng", inputDone = true)
+      cli.expect("<fn_func>")
+      cli.expect("<data>strng</data>")
+      cli.expect("<value>Hello,strng</value>")
+      cli.expect("</fn_func>")
+    } (ExitCode.Success)
   }
 
   /**
@@ -84,26 +59,12 @@ class TestCLIUdfs {
    * User Defined Function call
    */
   @Test def test_noUdfsLoaded_udf_schema(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[error] Schema Definition Error: Unsupported function: jsudf:replace")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+
+    runCLI(args"-v parse -s $schema -r user_func1") { cli =>
+      cli.send("strng", inputDone = true)
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: jsudf:replace")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -114,34 +75,15 @@ class TestCLIUdfs {
    * The schema makes a User Defined Function call
    */
   @Test def test_noUdfsLoaded_MissingClassInMetaInfFile(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaForNonExistentClass = "daffodil-udf/src/test/resources/org/badmetainf/nonexistentclass/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaForNonExistentClass))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[warn] User Defined Function Provider failed to load: org.apache.daffodil.udf.UserDefinedFunctionProvider:"),
-          contains("Provider org.nonexistentclass.example.StringFunctions.StringFunctionsProvider not found"),
-          contains("[error] Schema Definition Error: Unsupported function: jsudf:replace")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/resources/org/badmetainf/nonexistentclass/")
+
+    runCLI(args"-v parse -s $schema -r user_func1", classpath) { cli =>
+      cli.send("strng", inputDone = true)
+      cli.expectErr("[warn] User Defined Function Provider failed to load: org.apache.daffodil.udf.UserDefinedFunctionProvider:")
+      cli.expectErr("Provider org.nonexistentclass.example.StringFunctions.StringFunctionsProvider not found")
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: jsudf:replace")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -149,99 +91,45 @@ class TestCLIUdfs {
    * file, but a schema makes a User Defined Function call
    */
   @Test def test_noUdfsLoaded_MissingMetaInfFile(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val dafClassPath =
-      testUdfsPaths.mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[error] Schema Definition Error: Unsupported function: jsudf:replace")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath()
+
+    runCLI(args"-v parse -s $schema -r user_func1", classpath) { cli =>
+      cli.send("strng", inputDone = true)
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: jsudf:replace")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
    * Tests the case when a User Defined Function Provider returns null for its list of UDFs
    */
   @Test def test_UDFPClass_NoUdfClasses(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/java/org/badudfs/functionclasses1/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[warn] User Defined Function Provider ignored:" +
-            " org.badudfs.functionclasses1.StringFunctions.StringFunctionsProvider." +
-            " No User Defined Functions found."),
-          contains("[error] Schema Definition Error: Unsupported function: jsudf:replace")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/java/org/badudfs/functionclasses1/StringFunctions/")
+
+    runCLI(args"-v parse -s $schema -r user_func1", classpath) { cli =>
+      cli.send("strng", inputDone = true)
+      cli.expectErr("[warn] User Defined Function Provider ignored:" +
+        " org.badudfs.functionclasses1.StringFunctions.StringFunctionsProvider." +
+        " No User Defined Functions found.")
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: jsudf:replace")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
    * Tests the case when a User Defined Function Provider returns an empty list of UDFs
    */
   @Test def test_UDFPClass_emptyUdfClasses(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/java/org/badudfs/functionclasses2/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[warn] User Defined Function Provider ignored:" +
-            " org.badudfs.functionclasses2.StringFunctions.StringFunctionsProvider." +
-            " No User Defined Functions found."),
-          contains("[error] Schema Definition Error: Unsupported function: jsudf:replace")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/java/org/badudfs/functionclasses2/StringFunctions/")
+
+    runCLI(args"-v parse -s $schema -r user_func1", classpath) { cli =>
+      cli.sendLine("strng", inputDone = true)
+      cli.expectErr("[warn] User Defined Function Provider ignored:" +
+        " org.badudfs.functionclasses2.StringFunctions.StringFunctionsProvider." +
+        " No User Defined Functions found.")
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: jsudf:replace")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -249,38 +137,18 @@ class TestCLIUdfs {
    * interface
    */
   @Test def test_UDFClass_nonUDF(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/java/org/badudfs/nonUDF/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[warn] User Defined Function ignored:" +
-            " org.badudfs.nonUDF.StringFunctions.FuncA." +
-            " Doesn't implement org.apache.daffodil.udf.UserDefinedFunction"),
-          contains("[warn] User Defined Function ignored:" +
-            " org.badudfs.nonUDF.StringFunctions.Replace." +
-            " Doesn't implement org.apache.daffodil.udf.UserDefinedFunction"),
-          contains("[error] Schema Definition Error: Unsupported function: jsudf:replace")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/java/org/badudfs/nonUDF/StringFunctions/")
+
+    runCLI(args"-v parse -s $schema -r user_func1", classpath) { cli =>
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.nonUDF.StringFunctions.FuncA." +
+        " Doesn't implement org.apache.daffodil.udf.UserDefinedFunction")
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.nonUDF.StringFunctions.Replace." +
+        " Doesn't implement org.apache.daffodil.udf.UserDefinedFunction")
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: jsudf:replace")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -288,45 +156,21 @@ class TestCLIUdfs {
    * annotstion fields
    */
   @Test def test_UDFClass_nonAnn(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/java/org/badudfs/annotations/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[warn] User Defined Function ignored:" +
-            " org.badudfs.annotations.StringFunctions.FuncB." +
-            " Missing org.apache.daffodil.udf.UserDefinedFunctionIdentification annotation"),
-          anyOf(
-            contains("[warn] User Defined Function ignored:" +
-              " org.badudfs.annotations.StringFunctions.Compare." +
-              " Annotation namespace field is empty or invalid."),
-            contains("[warn] User Defined Function ignored:" +
-              " org.badudfs.annotations.StringFunctions.Compare." +
-              " Annotation name field is empty or invalid.")),
-          contains("[warn] User Defined Function ignored:" +
-            " org.badudfs.annotations.StringFunctions.Replace." +
-            " Annotation name field is empty or invalid."),
-          contains("[error] Schema Definition Error: Unsupported function: jsudf:replace")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/java/org/badudfs/annotations/StringFunctions/")
+
+    runCLI(args"-v parse -s $schema -r user_func1", classpath) { cli =>
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.annotations.StringFunctions.FuncB." +
+        " Missing org.apache.daffodil.udf.UserDefinedFunctionIdentification annotation")
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.annotations.StringFunctions.Compare." +
+        " Annotation namespace field is empty or invalid.")
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.annotations.StringFunctions.Replace." +
+        " Annotation name field is empty or invalid.")
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: jsudf:replace")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -337,50 +181,30 @@ class TestCLIUdfs {
    *   has unsupported return type
    */
   @Test def test_UDFClass_noEvaluate(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/java/org/badudfs/evaluate/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[warn] User Defined Function ignored:" +
-            " org.badudfs.evaluate.StringFunctions.FuncA." +
-            " Overloaded evaluate method: urn:example:com:ext:badudfs:stringfunctions:funcA"),
-          contains("[warn] User Defined Function ignored:" +
-            " org.badudfs.evaluate.StringFunctions.Replace." +
-            " Missing evaluate method: urn:example:com:ext:badudfs:stringfunctions:replace"),
-          contains("[warn] User Defined Function ignored:" +
-            " org.badudfs.evaluate.StringFunctions.FuncB." +
-            " Unsupported return type: void"),
-          contains("[warn] User Defined Function ignored:" +
-            " org.badudfs.evaluate.StringFunctions.FuncC." +
-            " Unsupported parameter type(s): String[],int[]"),
-          contains("[warn] User Defined Function ignored:" +
-            " org.badudfs.evaluate.StringFunctions.FuncD." +
-            " Unsupported parameter type(s): String[]"),
-          contains("[warn] User Defined Function ignored:" +
-            " org.badudfs.evaluate.StringFunctions.FuncE." +
-            " Unsupported return type: String[]"),
-          contains("[error] Schema Definition Error: Unsupported function: jsudf:replace")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/java/org/badudfs/evaluate/StringFunctions/")
+
+    runCLI(args"-v parse -s $schema -r user_func1", classpath) { cli =>
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.evaluate.StringFunctions.Replace." +
+        " Missing evaluate method: urn:example:com:ext:badudfs:stringfunctions:replace")
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.evaluate.StringFunctions.FuncA." +
+        " Overloaded evaluate method: urn:example:com:ext:badudfs:stringfunctions:funcA")
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.evaluate.StringFunctions.FuncB." +
+        " Unsupported return type: void")
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.evaluate.StringFunctions.FuncC." +
+        " Unsupported parameter type(s): String[],int[]")
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.evaluate.StringFunctions.FuncD." +
+        " Unsupported parameter type(s): String[]")
+      cli.expectErr("[warn] User Defined Function ignored:" +
+        " org.badudfs.evaluate.StringFunctions.FuncE." +
+        " Unsupported return type: String[]")
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: jsudf:replace")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -388,33 +212,14 @@ class TestCLIUdfs {
    *   throws custom error on evaluate
    */
   @Test def test_UDFClass_CustomExceptionOnEvaluate(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/scala/org/sbadudfs/udfexceptions/evaluating/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s parse -s %s -r user_func2", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[error] User Defined Function 'ssudf:reverse' Error. Cause: org.sbadudfs.udfexceptions.evaluating.StringFunctions.Reverse$CustomException: UDF Error!"),
-          contains("at org.sbadudfs.udfexceptions.evaluating.StringFunctions.Reverse.evaluate(StringFunctionsProvider.scala:56)")))
-
-      Util.expectExitCode(ExitCode.UserDefinedFunctionError, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/scala/org/sbadudfs/udfexceptions/evaluating/StringFunctions/")
+
+    runCLI(args"parse -s $schema -r user_func2", classpath) { cli =>
+      cli.send("strng", inputDone = true)
+      cli.expectErr("[error] User Defined Function 'ssudf:reverse' Error. Cause: org.sbadudfs.udfexceptions.evaluating.StringFunctions.Reverse$CustomException: UDF Error!")
+      cli.expectErr("at org.sbadudfs.udfexceptions.evaluating.StringFunctions.Reverse.evaluate(StringFunctionsProvider.scala:56)")
+    } (ExitCode.UserDefinedFunctionError)
   }
 
   /**
@@ -422,29 +227,12 @@ class TestCLIUdfs {
    *   throws processing error on evaluate
    */
   @Test def test_UDFClass_ProcessingErrorOnEvaluate(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/scala/org/sbadudfs/udfexceptions/evaluating/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s parse -s %s -r user_func3", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(1, contains("[error] Schema Definition Error: User Defined Function 'ssudf:rev-words' Error: UDF PE!"))
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/scala/org/sbadudfs/udfexceptions/evaluating/StringFunctions/")
 
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"parse -s $schema -r user_func3", classpath) { cli =>
+      cli.expectErr("[error] Schema Definition Error: User Defined Function 'ssudf:rev-words' Error: UDF PE!")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -452,34 +240,14 @@ class TestCLIUdfs {
    *   throws an error while being loaded
    */
   @Test def test_UDFClass_exceptionOnLoad(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/scala/org/sbadudfs/udfexceptions2/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func3", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[error] User Defined Function could not be initialized: {http://example.com/scala/udf}rev-words."),
-          contains("Cause: org.sbadudfs.udfexceptions2.StringFunctions.ReverseWords$CustomException: UDF Error!"),
-          contains("at org.sbadudfs.udfexceptions2.StringFunctions.ReverseWords.<init>(StringFunctionsProvider.scala:65)")))
-
-      Util.expectExitCode(ExitCode.UserDefinedFunctionError, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/scala/org/sbadudfs/udfexceptions2/StringFunctions/")
+
+    runCLI(args"-v parse -s $schema -r user_func3", classpath) { cli =>
+      cli.expectErr("[error] User Defined Function could not be initialized: {http://example.com/scala/udf}rev-words.")
+      cli.expectErr("Cause: org.sbadudfs.udfexceptions2.StringFunctions.ReverseWords$CustomException: UDF Error!")
+      cli.expectErr("at org.sbadudfs.udfexceptions2.StringFunctions.ReverseWords.<init>(StringFunctionsProvider.scala:65)")
+    } (ExitCode.UserDefinedFunctionError)
   }
 
   /**
@@ -487,36 +255,16 @@ class TestCLIUdfs {
    *   throws an error while loading its UDFs
    */
   @Test def test_UDFPClass_exceptionOnLoadingUDFs(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/scala/org/sbadudfs/udfpexceptions/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func3", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[warn] User Defined Function Provider ignored:"),
-          contains("org.sbadudfs.udfpexceptions.StringFunctions.StringFunctionsProvider"),
-          contains("Error loading User Defined Functions:"),
-          contains("org.sbadudfs.udfpexceptions.StringFunctions.StringFunctionsProvider$CustomException"),
-          contains("[error] Schema Definition Error: Unsupported function: ssudf:rev-words")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/scala/org/sbadudfs/udfpexceptions/StringFunctions/")
+
+    runCLI(args"-v parse -s $schema -r user_func3", classpath) { cli =>
+      cli.expectErr("[warn] User Defined Function Provider ignored:")
+      cli.expectErr("org.sbadudfs.udfpexceptions.StringFunctions.StringFunctionsProvider")
+      cli.expectErr("Error loading User Defined Functions:")
+      cli.expectErr("org.sbadudfs.udfpexceptions.StringFunctions.StringFunctionsProvider$CustomException")
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: ssudf:rev-words")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -524,34 +272,14 @@ class TestCLIUdfs {
    *   throws an error while being loaded
    */
   @Test def test_UDFPClass_exceptionOnLoad(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/scala/org/sbadudfs/udfpexceptions2/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func3", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[warn] User Defined Function Provider failed to load: org.apache.daffodil.udf.UserDefinedFunctionProvider"),
-          contains("Provider org.sbadudfs.udfpexceptions2.StringFunctions.StringFunctionsProvider could not be instantiated"),
-          contains("[error] Schema Definition Error: Unsupported function: ssudf:rev-words")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/scala/org/sbadudfs/udfpexceptions2/StringFunctions/")
+
+    runCLI(args"-v parse -s $schema -r user_func3", classpath) { cli =>
+      cli.expectErr("[warn] User Defined Function Provider failed to load: org.apache.daffodil.udf.UserDefinedFunctionProvider")
+      cli.expectErr("Provider org.sbadudfs.udfpexceptions2.StringFunctions.StringFunctionsProvider could not be instantiated")
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: ssudf:rev-words")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -560,35 +288,15 @@ class TestCLIUdfs {
    *   class object
    */
   @Test def test_UDFPClass_incorrectUDFObject(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/scala/org/sbadudfs/functionclasses/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func3", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[warn] User Defined Function class mismatch: {http://example.com/scala/udf}rev-words."),
-          contains("Expected: class org.sbadudfs.functionclasses.StringFunctions.ReverseWords"),
-          contains("Actual: class org.sbadudfs.functionclasses.StringFunctions.Reverse"),
-          contains("[error] Schema Definition Error: Unsupported function: ssudf:rev-words")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/scala/org/sbadudfs/functionclasses/StringFunctions/")
+
+    runCLI(args"-v parse -s $schema -r user_func3", classpath) { cli =>
+      cli.expectErr("[warn] User Defined Function class mismatch: {http://example.com/scala/udf}rev-words.")
+      cli.expectErr("Expected: class org.sbadudfs.functionclasses.StringFunctions.ReverseWords")
+      cli.expectErr("Actual: class org.sbadudfs.functionclasses.StringFunctions.Reverse")
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: ssudf:rev-words")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -596,34 +304,14 @@ class TestCLIUdfs {
    *   incorrectly implements createUserDefinedFunction that results in an exception
    */
   @Test def test_UDFPClass_incorrectUDFCreateImplementation(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/scala/org/sbadudfs/functionclasses/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format(Util.echoN("strng") + "| %s -v parse -s %s -r user_func2", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[error] User Defined Function could not be initialized: {http://example.com/scala/udf}reverse."),
-          contains("Cause: scala.MatchError: http://example.com/scala/udf:reverse (of class java.lang.String)"),
-          contains("at org.sbadudfs.functionclasses.StringFunctions.StringFunctionsProvider.createUserDefinedFunction(StringFunctionsProvider.scala:34)")))
-
-      Util.expectExitCode(ExitCode.UserDefinedFunctionError, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/scala/org/sbadudfs/functionclasses/StringFunctions/")
+
+    runCLI(args"-v parse -s $schema -r user_func2", classpath) { cli =>
+      cli.expectErr("[error] User Defined Function could not be initialized: {http://example.com/scala/udf}reverse.")
+      cli.expectErr("Cause: scala.MatchError: http://example.com/scala/udf:reverse (of class java.lang.String)")
+      cli.expectErr("at org.sbadudfs.functionclasses.StringFunctions.StringFunctionsProvider.createUserDefinedFunction(StringFunctionsProvider.scala:34)")
+    } (ExitCode.UserDefinedFunctionError)
   }
 
   /**
@@ -631,39 +319,16 @@ class TestCLIUdfs {
    *    contains a non serializable member
    */
   @Test def test_UDFClass_serializability(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/scala/org/sbadudfs/functionclasses2/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmd = String.format("%s -vv save-parser -s %s -r user_func4", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[debug] User Defined Function loaded: org.sbadudfs.functionclasses2.StringFunctions.GetNonSerializableState => {http://example.com/scala/udf}get-nonserializable-state"),
-          contains("[debug] User Defined Function loaded: org.sbadudfs.functionclasses2.StringFunctions.GetSerializableState => {http://example.com/scala/udf}get-serializable-state")))
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[warn] User Defined Function is not serializable: org.sbadudfs.functionclasses2.StringFunctions.GetNonSerializableState."),
-          contains("Could not serialize member of class: org.sbadudfs.functionclasses2.StringFunctions.SomeNonSerializableClass"),
-          contains("[error] Schema Definition Error: Unsupported function: ssudf:get-nonserializable-state")))
-
-      Util.expectExitCode(ExitCode.UnableToCreateProcessor, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/scala/org/sbadudfs/functionclasses2/StringFunctions/")
+
+    runCLI(args"-vv save-parser -s $schema -r user_func4", classpath) { cli =>
+      cli.expectErr("[debug] User Defined Function loaded: org.sbadudfs.functionclasses2.StringFunctions.GetNonSerializableState => {http://example.com/scala/udf}get-nonserializable-state")
+      cli.expectErr("[debug] User Defined Function loaded: org.sbadudfs.functionclasses2.StringFunctions.GetSerializableState => {http://example.com/scala/udf}get-serializable-state")
+      cli.expectErr("[warn] User Defined Function is not serializable: org.sbadudfs.functionclasses2.StringFunctions.GetNonSerializableState.")
+      cli.expectErr("Could not serialize member of class: org.sbadudfs.functionclasses2.StringFunctions.SomeNonSerializableClass")
+      cli.expectErr("[error] Schema Definition Error: Unsupported function: ssudf:get-nonserializable-state")
+    } (ExitCode.UnableToCreateProcessor)
   }
 
   /**
@@ -671,45 +336,22 @@ class TestCLIUdfs {
    *    contains serializable member
    */
   @Test def test_UDFClass_serializability2(): Unit = {
-    val schemaFile = Util.daffodilPath("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val savedParserFile = java.io.File.createTempFile("testParser_", ".tmp")
-    savedParserFile.deleteOnExit
-    val metaInfForSomeUdfA = "daffodil-udf/src/test/scala/org/sbadudfs/functionclasses2/StringFunctions/"
-
-    val dafClassPath =
-      (testUdfsPaths :+ Util.daffodilPath(metaInfForSomeUdfA))
-        .mkString(java.io.File.pathSeparator)
-
-    val shell = Util.start("", envp = Map("DAFFODIL_CLASSPATH" -> dafClassPath))
-
-    try {
-      val cmds = Array(
-        String.format("%s -vv save-parser -s %s -r user_func5 %s", Util.binPath, testSchemaFile, savedParserFile.getAbsolutePath),
-        String.format(Util.echoN("strng") + "| %s -v parse -P %s", Util.binPath, savedParserFile.getAbsolutePath))
-      val cmd = Util.makeMultipleCmds(cmds)
-      shell.sendLine(cmd)
-      shell.expectIn(
-        1,
-        allOf(
-          contains("[debug] User Defined Function loaded: org.sbadudfs.functionclasses2.StringFunctions.GetNonSerializableState => {http://example.com/scala/udf}get-nonserializable-state"),
-          contains("[debug] User Defined Function loaded: org.sbadudfs.functionclasses2.StringFunctions.GetSerializableState => {http://example.com/scala/udf}get-serializable-state")))
-      shell.expectIn(
-        0,
-        allOf(
-          contains("<user_func5>"),
-          contains("<data>strng</data>"),
-          contains("<value>Serializable State</value>"),
-          contains("</user_func5>")))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-      savedParserFile.delete
+    val schema = path("daffodil-udf/src/test/resources/org/apache/daffodil/udf/genericUdfSchema.xsd")
+    val classpath = udfClasspath("daffodil-udf/src/test/scala/org/sbadudfs/functionclasses2/StringFunctions/")
+
+    withTempFile { parser =>
+      runCLI(args"-vv save-parser -s $schema -r user_func5 $parser", classpath) { cli =>
+        cli.expectErr("[debug] User Defined Function loaded: org.sbadudfs.functionclasses2.StringFunctions.GetNonSerializableState => {http://example.com/scala/udf}get-nonserializable-state")
+        cli.expectErr("[debug] User Defined Function loaded: org.sbadudfs.functionclasses2.StringFunctions.GetSerializableState => {http://example.com/scala/udf}get-serializable-state")
+      } (ExitCode.Success)
+
+      runCLI(args"-v parse -P $parser", classpath) { cli =>
+        cli.send("strng", inputDone = true)
+        cli.expect("<user_func5>")
+        cli.expect("<data>strng</data>")
+        cli.expect("<value>Serializable State</value>")
+        cli.expect("</user_func5>")
+      } (ExitCode.Success)
     }
   }
 }
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/unparsing/TestCLIUnparsing.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/unparsing/TestCLIUnparsing.scala
index 6e9ebbb55..205e16647 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/unparsing/TestCLIUnparsing.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/unparsing/TestCLIUnparsing.scala
@@ -17,679 +17,319 @@
 
 package org.apache.daffodil.unparsing
 
+import java.nio.charset.StandardCharsets.UTF_8
+
+import org.apache.commons.io.FileUtils
+
 import org.junit.Assert._
 import org.junit.Test
-import java.io.File
-import org.apache.daffodil.CLI.Util
-import net.sf.expectit.matcher.Matchers.contains
-import net.sf.expectit.matcher.Matchers.eof
+
+import org.apache.daffodil.CLI.Util._
 import org.apache.daffodil.Main.ExitCode
 
 class TestCLIunparsing {
 
   @Test def test_3525_CLI_Unparsing_SimpleUnparse_inFile(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input12.txt")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input12.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s unparse -s %s --root e1 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("Hello"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"unparse -s $schema --root e1 $input") { cli =>
+      cli.expect("Hello")
+    } (ExitCode.Success)
   }
 
   @Test def test_3526_CLI_Unparsing_SimpleUnparse_inFile2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input13.txt")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input13.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s unparse -s %s --root e3 %s", Util.binPath, testSchemaFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("[1,2]"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"unparse -s $schema --root e3 $input") { cli =>
+      cli.expect("[1,2]")
+    } (ExitCode.Success)
   }
 
   @Test def test_3527_CLI_Unparsing_SimpleUnparse_stdin(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input14.txt")
-    val (testSchemaFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(inputFile)) else (schemaFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-	  val cmd = String.format(Util.cat(testInputFile) + "| %s unparse -s %s --root e3", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("[1,2]"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"unparse -s $schema --root e3") { cli =>
+      cli.sendFile(input, inputDone = true)
+      cli.expect("[1,2]")
+    } (ExitCode.Success)
   }
 
   @Test def test_3528_CLI_Unparsing_SimpleUnparse_stdin2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("\"<tns:e1 xmlns:tns='http://example.com'>Hello</tns:e1>\"") + "| %s unparse -s %s --root e1", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("Hello"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"unparse -s $schema --root e1") { cli =>
+      cli.send("<tns:e1 xmlns:tns='http://example.com'>Hello</tns:e1>", inputDone = true)
+      cli.expect("Hello")
+    } (ExitCode.Success)
   }
 
   @Test def test_3529_CLI_Unparsing_SimpleUnparse_stdin3(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format(Util.echoN("\"<tns:e1 xmlns:tns='http://example.com'>Hello</tns:e1>\"") + "| %s unparse -s %s --root e1 -", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("Hello"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"unparse -s $schema --root e1 -") { cli =>
+      cli.send("<tns:e1 xmlns:tns='http://example.com'>Hello</tns:e1>", inputDone = true)
+      cli.expect("Hello")
+    } (ExitCode.Success)
   }
 
   @Test def test_3584_CLI_Unparsing_SimpleUnparse_stdin4(): Unit = {
-
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
-    val (testSchemaFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile)) else (schemaFile)
-
-    val shell = Util.start("")
-
-    try {
-      val input = "\"<tns:file xmlns:tns='http://www.example.org/example1/'><tns:header><tns:title>1</tns:title><tns:title>2</tns:title><tns:title>3</tns:title></tns:header><tns:record><tns:item>4</tns:item><tns:item>5</tns:item><tns:item>6</tns:item></tns:record></tns:file>\""
-      val cmd = String.format(Util.echoN(input) + "| %s unparse -s %s --root file", Util.binPath, testSchemaFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("1,2,3"))
-      shell.expect(contains("4,5,6"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"unparse -s $schema --root file") { cli =>
+      val input = "<tns:file xmlns:tns='http://www.example.org/example1/'><tns:header><tns:title>1</tns:title><tns:title>2</tns:title><tns:title>3</tns:title></tns:header><tns:record><tns:item>4</tns:item><tns:item>5</tns:item><tns:item>6</tns:item></tns:record></tns:file>"
+      cli.send(input, inputDone = true)
+      cli.expect("1,2,3")
+      cli.expect("4,5,6")
+    } (ExitCode.Success)
   }
 
   @Test def test_3574_CLI_Unparsing_SimpleUnparse_extVars(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+    val config = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input15.txt")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
-    val configFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input15.txt")
-    val (testSchemaFile, testConfigFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(configFile), Util.cmdConvert(inputFile)) else (schemaFile, configFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s unparse -s %s -r row -D\"{http://example.com}var1=99\" -c %s %s", Util.binPath, testSchemaFile, testConfigFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("0"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"unparse -s $schema -r row -D{http://example.com}var1=99 -c $config $input") { cli =>
+      cli.expect("0")
+    } (ExitCode.Success)
   }
 
   @Test def test_3575_CLI_Unparsing_SimpleUnparse_extVars2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+    val config = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
+    val input = path("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input16.txt")
 
-    val schemaFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
-    val configFile = Util.daffodilPath("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
-    val inputFile = Util.daffodilPath("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/input/input16.txt")
-    val (testSchemaFile, testConfigFile, testInputFile) = if (Util.isWindows) (Util.cmdConvert(schemaFile), Util.cmdConvert(configFile), Util.cmdConvert(inputFile)) else (schemaFile, configFile, inputFile)
-
-    val shell = Util.start("")
-
-    try {
-      val cmd = String.format("%s unparse -s %s -r row -c %s %s", Util.binPath, testSchemaFile, testConfigFile, testInputFile)
-      shell.sendLine(cmd)
-      shell.expect(contains("0"))
-
-      Util.expectExitCode(ExitCode.Success, shell)
-      shell.send("exit\n")
-      shell.expect(eof)
-      shell.close()
-    } finally {
-      shell.close()
-    }
+    runCLI(args"unparse -s $schema -r row -c $config $input") { cli =>
+      cli.expect("0")
+    } (ExitCode.Success)
   }
 
   @Test def test_3582_CLI_Unparsing_SimpleUnparse_outFile(): Unit = {
... 1238 lines suppressed ...