Repository: buildtesters/buildtest (language: python)

Issue:
bump version to 1.4



Discussion / hints:

Fix patch:
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
index 8915af44c..1b2ac7b30 100644
--- a/.github/ISSUE_TEMPLATE/bug.yml
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -10,8 +10,7 @@ body:
     attributes:
       label: Bug Description
       description: |
-        Please describe the bug you are facing with an output of buildtest command and expected error. In order for us to troubleshoot your issue,
-        we request you provide list of steps to reproduce the error. Please include content of buildspec file if issue is related.
+        Please describe the bug you are facing with an output of buildtest command and expected error. Please include content of buildspec file if issue is related.
       placeholder: |
         ```console
           $ buildtest <command>
@@ -19,38 +18,32 @@ body:
 
         Include content of buildspec if necessary
         ```yaml
-            version: "1.0"
             buildspecs:
               hello_world:
                 type: script
                 executor: generic.local.sh
                 run: echo "hello world"
         ```
+    validations:
+      required: true
 
+  - type: textarea
+    id: reproduce-error
+    attributes:
+      label: Steps to reproduce the error
+      description: |
+        Please provide a list of steps to reproduce the error in-order for us to troubleshoot your issue.
     validations:
       required: true
-  - type: dropdown
-    id: version
+
+  - type: textarea
+    id: version-commit
     attributes:
-      label: Version
-      description: What version of buildtest are you using?
-      options:
-        - devel
-        - master
-        - v0.11.0
-        - v0.10.2
-        - v0.10.1
-        - v0.10.0
-        - v0.9.6
-        - v0.9.5
-        - v0.9.4
-        - v0.9.3
-        - v0.9.2
-        - v0.9.1
-        - v0.9.0
-        - v0.8.1
-        - v0.8.0
-        - other
+      label: Version and HEAD commit
+      description: |
+        Please copy and paste output of the commands `buildtest --version` and `git log -1` in order for us to know the
+        version of the buildtest you are using and the HEAD commit. This will be automatically formatted into code, so no need for backticks.
+      render: shell
     validations:
       required: true
 
@@ -58,8 +51,11 @@ body:
     id: logs
     attributes:
       label: Relevant log output
-      description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+      description: |
+        Please copy and paste any relevant log output. Please provide the output of the commands `buildtest info` and `buildtest debugreport`.
       render: shell
+    validations:
+      required: false
 
   - type: checkboxes
     id: slackterms
diff --git a/.github/ISSUE_TEMPLATE/cli_feature.yml b/.github/ISSUE_TEMPLATE/cli_feature.yml
index 5e5c60727..803c4bac5 100644
--- a/.github/ISSUE_TEMPLATE/cli_feature.yml
+++ b/.github/ISSUE_TEMPLATE/cli_feature.yml
@@ -1,4 +1,4 @@
-name: Buildtest Command Line Feature Request
+name: Feature Request
 description: Feature request for buildtest program
 title: "[FEATURE]: "
 labels: [feature, triage]
@@ -8,11 +8,39 @@ body:
   - type: textarea
     id: feature_description
     attributes:
-      label: Please describe your feature request
-      description: Please provide a detailed summary of the feature request including name of buildtest command (buildtest build) and options including intended behavior
+      label: Please describe your feature
+      description: Please provide a detailed summary of the feature including its intended behavior. Please attach any related issues or pull requests
+        and relevant example of Buildspec or Buildtest configuration if necessary.
+      placeholder: |
+        Describe your feature here
+
+        Include Buildspec or Buildtest configuration here if necessary
+        ```yaml
+            buildspecs:
+              hello_world:
+                type: script
+                executor: generic.local.sh
+                run: echo "hello world"
+        ```
     validations:
       required: true
 
+  - type: textarea
+    id: potential_solution
+    attributes:
+      label: Suggest potential solution
+      description: Please describe a solution or action items you would like to propose
+    validations:
+      required: false
+
+  - type: textarea
+    id: notes
+    attributes:
+      label: Additional Information
+      description: Please provide any additional information that may include screenshots or attachments that can help describe your feature request.
+    validations:
+      required: false
+
   - type: checkboxes
     id: slackterms
     attributes:
diff --git a/.gitlab/ascent.yml b/.gitlab/ascent.yml
index 0eb5628e0..3d0f10c10 100644
--- a/.gitlab/ascent.yml
+++ b/.gitlab/ascent.yml
@@ -27,7 +27,9 @@ ascent_pr_regression_test:
     - pip install -r docs/requirements.txt
     - python $BUILDTEST_ROOT/buildtest/tools/unittests.py -c
     - echo $?
+    - curl -Os https://uploader.codecov.io/latest/linux/codecov
+    - chmod +x codecov
     # CODECOV_TOKEN environment must be set, this value is stored in CI/CD variable at https://code.ornl.gov/ecpcitest/buildtest/-/settings/ci_cd
-    - codecov -t $CODECOV_TOKEN
+    - ./codecov -t $CODECOV_TOKEN
     - source deactivate
     - rm -rf $CI_PROJECT_DIR/.conda
diff --git a/.gitlab/nersc.yml b/.gitlab/nersc.yml
index 49f16202a..4db8d6ca2 100644
--- a/.gitlab/nersc.yml
+++ b/.gitlab/nersc.yml
@@ -6,31 +6,6 @@ variables:
 stages:
   - regression
 
-cori_pr_regression_test:
-  tags: ["cori"]
-  stage: regression
-  rules:
-    - if: '$CI_PIPELINE_SOURCE == "external_pull_request_event" && $CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
-      changes:
-        - buildtest/**/*
-        - tests/**/*
-
-  script:
-    - set -e
-    - whoami
-    - module load python/3.9-anaconda-2021.11
-    - git branch
-    - conda create -p $CI_PROJECT_DIR/.conda python=3.9 -y
-    - source activate $CI_PROJECT_DIR/.conda
-    - source setup.sh
-    - pip install -r docs/requirements.txt
-    - python $BUILDTEST_ROOT/buildtest/tools/unittests.py -c
-    - echo $?
-    # CODECOV_TOKEN environment must be set, this value is stored in CI/CD variable at https://software.nersc.gov/NERSC/buildtest/-/settings/ci_cd
-    - codecov -t $CODECOV_TOKEN
-    - source deactivate
-    - conda env remove -p $CI_PROJECT_DIR/.conda -y
-
 perlmutter_regression_test:
   tags: ["bdtest-perlmutter"]
   stage: regression
@@ -52,6 +27,8 @@ perlmutter_regression_test:
     - pip install -r docs/requirements.txt
     - python $BUILDTEST_ROOT/buildtest/tools/unittests.py -c
     - echo $?
+    - curl -Os https://uploader.codecov.io/latest/linux/codecov
+    - chmod +x codecov
     # CODECOV_TOKEN environment must be set, this value is stored in CI/CD variable at https://software.nersc.gov/NERSC/buildtest/-/settings/ci_cd
     - codecov -t $CODECOV_TOKEN
     - source deactivate
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c81ea63d4..57d21c1d1 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,35 @@
 CHANGELOG
 =========
 
+v1.4 (May 4th, 2023)
+----------------------
+
+**Buildspec Changes**
+
+- Add support for logical AND in status check by introducing a new property ``mode`` `#1429 <https://github.com/buildtesters/buildtest/pull/1429>`_
+- Remove `suite` property in spack schema to ensure suitenames are auto-generated when running `spack test results` `1451 <https://github.com/buildtesters/buildtest/pull/1451>`_
+
+**Command Line Changes**
+
+- Add support for building multiple tags by comma separated list ``buildtest build --tags tag1,tag2,tag3``. `1419 <https://github.com/buildtesters/buildtest/pull/1419>`_
+- Add option ``buildtest config compilers find --file`` to save configuration file in alternative location. `#1420 <https://github.com/buildtesters/buildtest/pull/1420>`_
+- Add pagination support for ``buildtest history`` and ``buildtest inspect``. `#1424 <https://github.com/buildtesters/buildtest/pull/1424>`_, `#1428 <https://github.com/buildtesters/buildtest/pull/1428>`_
+- Add global option ``--row-count`` that can be used with ``buildtest inspect``, ``buildtest buildspec find``, ``buildtest buildspec invalid``, ``buildtest buildspec maintainers``, ``buildtest report`` and ``buildtest history`` command. `#1463 <https://github.com/buildtesters/buildtest/pull/1463>`_
+**Configuration Changes**
+
+- Add support for ``buildspecs`` property in configuration file to configure behavior of ``buildtest buildspec find`` command. `#1455 <https://github.com/buildtesters/buildtest/pull/1455>`_
+- Add support for ``report`` section in configuration file to control behavior of ``buildtest report`` command `1459 <https://github.com/buildtesters/buildtest/pull/1459>`_
+- Rename ``buildspecs_root`` to ``root`` under the ``buildspecs`` section. `#1457 <https://github.com/buildtesters/buildtest/pull/1457>`_
+- Change pattern property for executor names to be alphanumeric `#1440 <https://github.com/buildtesters/buildtest/pull/1440>`_
+
+**Misc Updates**
+
+- Update github issue templates `#1430 <https://github.com/buildtesters/buildtest/pull/1430>`_, `#1431 <https://github.com/buildtesters/buildtest/pull/1431>`_
+- Fix issue with tab-completion for subcommands at top-level options `#1432 <https://github.com/buildtesters/buildtest/pull/1432>`_
+- Remove ``codecov`` dependency `#1436 <https://github.com/buildtesters/buildtest/pull/1436>`_
+- Add documentation on Assert Less Than `#1439 <https://github.com/buildtesters/buildtest/pull/1439>`_
+- Remove CI job for Cori and refactor tests `#1442 <https://github.com/buildtesters/buildtest/pull/1442>`_
+
 v1.3 (Mar 7th, 2023)
 ----------------------
 
diff --git a/README.rst b/README.rst
index 6d5952fe9..6dbe6503e 100644
--- a/README.rst
+++ b/README.rst
@@ -7,6 +7,7 @@
     :target: https://readthedocs.org/projects/buildtest/builds/
 
 .. |license| image:: https://img.shields.io/github/license/buildtesters/buildtest.svg
+   :target: https://github.com/buildtesters/buildtest/blob/devel/LICENSE
 
 .. |release| image:: https://img.shields.io/github/v/release/buildtesters/buildtest.svg
    :target: https://github.com/buildtesters/buildtest/releases
@@ -18,10 +19,13 @@
     :target: https://github.com/buildtesters/buildtest/pulls
     
 .. |commit_activity_yearly| image:: https://img.shields.io/github/commit-activity/y/buildtesters/buildtest.svg
- 
+   :target: https://github.com/buildtesters/buildtest/pulse
+
 .. |commit_activity_monthly| image:: https://img.shields.io/github/commit-activity/m/buildtesters/buildtest.svg
+   :target: https://github.com/buildtesters/buildtest/pulse
 
 .. |core_infrastructure| image:: https://bestpractices.coreinfrastructure.org/projects/3469/badge
+   :target: https://bestpractices.coreinfrastructure.org/projects/3469
 
 .. |codecov| image:: https://codecov.io/gh/buildtesters/buildtest/branch/devel/graph/badge.svg
     :target: https://codecov.io/gh/buildtesters/buildtest
@@ -56,7 +60,7 @@
 
 .. |zenodo| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.3967143.svg
    :target: https://doi.org/10.5281/zenodo.3967143
-   
+
 buildtest
 ---------
 
diff --git a/bash_completion.sh b/bash_completion.sh
index fe92f058b..087e5707c 100644
--- a/bash_completion.sh
+++ b/bash_completion.sh
@@ -99,7 +99,7 @@ _failed_tests()
 
 _avail_maintainers()
 {
-  buildtest buildspec maintainers --terse -l --no-header | sort
+  buildtest buildspec maintainers --terse -l --no-header --row-count | sort
 }
 
 # list of filterfields
@@ -128,12 +128,35 @@ _buildtest ()
 {
   local cur="${COMP_WORDS[COMP_CWORD]}"
   local prev="${COMP_WORDS[COMP_CWORD-1]}"
+  declare -i offset="0"
 
   COMPREPLY=()   # Array variable storing the possible completions.
 
-  next=${COMP_WORDS[1]}
+  declare -a buildtest_opts=("--color" "--config" "--debug" "--editor" "--help" "--helpcolor" "--logpath" "--loglevel" "--print-log" "--no-color" "--report" "--version" "--view-log" "-c" "-d" "-h" "-l" "-p" "-r" "-V")
 
+  commands_with_input=( "--color" "--config" "-c" "--report" "-r" "--loglevel" "-l" "--editor" )   # Array variable storing commands which require an input argument from the user.
 
+  for command in "${COMP_WORDS[@]}"
+  do
+    for element in "${buildtest_opts[@]}"
+    do
+
+        if [[ "$command" == "$element" ]]; then
+          
+          if [[ "${commands_with_input[*]}" =~ $command ]];
+          then
+            ((offset+=2))
+          else
+            ((offset+=1))
+          fi
+        fi
+
+    done
+
+  done
+
+  local next=${COMP_WORDS[1+offset]}
+  
   case "$next" in
   #case "${prev}" in
     build|bd)
@@ -205,7 +228,7 @@ _buildtest ()
         COMPREPLY=( $( compgen -W "$(_avail_report_formatfields)" -- $cur ) )
         return
       esac
-      case "$prev" in summary)
+      case "$prev" in summary|sm)
         local opts="-d -h --detailed --help"
         COMPREPLY=( $( compgen -W "${opts}" -- $cur ) )
         return
@@ -218,15 +241,15 @@ _buildtest ()
       COMPREPLY=( $( compgen -W "${cmds}" -- $cur ) )
       # handle completion logic for 'buildtest config <subcommand>' based on subcommands
 
-      case "${COMP_WORDS[COMP_CWORD-2]}" in
+      case "${COMP_WORDS[2+offset]}" in
         compilers|co)
           local opts="--help --json --yaml -h -j -y find test"
           COMPREPLY=( $( compgen -W "${opts}" -- $cur ) )
           if [[ "${prev}" == "find" ]]; then
-            local opts="--detailed --help --modulepath --update -d -h -m -u"
+            local opts="--detailed --file --help --modulepath --update -d -h -m -u"
             COMPREPLY=( $( compgen -W "${opts}" -- $cur ) )
           fi
-          if [[ "${prev}" == "test"  ]]; then
+          if [[ "${prev}" == "test" ]]; then
             COMPREPLY=( $( compgen -W "$(_avail_compilers)" -- $cur ) )
           fi
           ;;
@@ -253,9 +276,9 @@ _buildtest ()
       COMPREPLY=( $( compgen -W "${cmds}" -- $cur ) )
 
       # case statement to handle completion for buildtest inspect [name|id|list] command
-      case "${COMP_WORDS[2]}" in
+      case "${COMP_WORDS[2+offset]}" in
         list|l)
-          local opts="--builder --help --no-header --pager --terse -b -h -n -t"
+          local opts="--builder --help --no-header --pager --row-count --terse -b -h -n -t"
           COMPREPLY=( $( compgen -W "${opts}" -- $cur ) );;
         name|n)
           COMPREPLY=( $( compgen -W "$(_test_name)" -- $cur ) )
@@ -276,7 +299,7 @@ _buildtest ()
         query|q)
           COMPREPLY=( $( compgen -W "$(_builder_names)" -- $cur ) )
           if [[ $cur == -* ]] ; then
-            local opts="--buildscript --buildenv --error --help --output --testpath --theme -b -be -e -o -h -o -t"
+            local opts="--buildscript --buildenv --error --help --output --pager --testpath --theme -b -be -e -o -h -o -t"
             COMPREPLY=( $( compgen -W "${opts}" -- $cur ) )
           fi
           case "${prev}" in --theme)
@@ -292,12 +315,12 @@ _buildtest ()
       COMPREPLY=( $( compgen -W "${cmds}" -- $cur ) )
 
       # switch based on 2nd word 'buildtest buildspec <subcommand>'
-      case ${COMP_WORDS[2]} in
+      case ${COMP_WORDS[2+offset]} in
       find|f)
-         case ${COMP_WORDS[3]} in
+         case ${COMP_WORDS[3+offset]} in
          # completion for 'buildtest buildspec find invalid'
          invalid)
-           local opts="--error --help -e -h"
+           local opts="--error --help --row-count -e -h"
            COMPREPLY=( $( compgen -W "${opts}" -- $cur ) );;
          # completion for rest of arguments
          *)
@@ -318,7 +341,7 @@ _buildtest ()
          esac
         ;;
       summary|sm)
-         case ${COMP_WORDS[3]} in
+         case ${COMP_WORDS[3+offset]} in
          # completion for rest of arguments
          *)
            local longopts="--help --pager"
@@ -355,10 +378,10 @@ _buildtest ()
         esac
         ;;
       maintainers|m)
-        local opts="--breakdown --list --help --terse --no-header -b -h -l -n find"
+        local opts="--breakdown --help --list --row-count --terse --no-header -b -h -l -n find"
         COMPREPLY=( $( compgen -W "${opts}" -- $cur ) )
 
-        case ${COMP_WORDS[3]} in
+        case ${COMP_WORDS[3+offset]} in
         find)
           COMPREPLY=( $( compgen -W "$(_avail_maintainers)" -- $cur ) );;
         esac
@@ -383,9 +406,9 @@ _buildtest ()
       local cmds="--help --pager -h list query"
       COMPREPLY=( $( compgen -W "${cmds}" -- $cur ) )
 
-      case ${COMP_WORDS[2]} in
+      case ${COMP_WORDS[2+offset]} in
       list)
-        local opts="--help --no-header --terse -h -n -t"
+        local opts="--help --no-header --row-count --terse -h -n -t"
         COMPREPLY=( $( compgen -W "${opts}" -- $cur ) )
         ;;
       query)
@@ -450,6 +473,9 @@ _buildtest ()
           if [[ "${prev}" == "--loglevel" ]] || [[ "${prev}" == "-l" ]]; then
             COMPREPLY=( $( compgen -W "DEBUG INFO WARNING ERROR CRITICAL" -- $cur ) )
           fi
+          if [[ "${prev}" == "--editor" ]]; then
+            COMPREPLY=( $( compgen -W "vi vim emacs nano" -- $cur ) )
+          fi
           ;;
       esac
   esac
diff --git a/docs/buildspecs/buildspec_overview.rst b/docs/buildspecs/buildspec_overview.rst
index df0f5e7c1..aa859e716 100644
--- a/docs/buildspecs/buildspec_overview.rst
+++ b/docs/buildspecs/buildspec_overview.rst
@@ -245,7 +245,8 @@ Currently, we can match state based on the following:
   - :ref:`Performance Check <perf_checks>`
   - :ref:`Explicit Test Status <explicit_status>`
   - :ref:`File Checks <file_checks>`
-  - :ref:`Symbolic Link Check <symlink_check>`
+  - :ref:`File Count <file_count>`
+
 
 .. _returncode:
 
@@ -588,6 +589,29 @@ We can try building this test by running the following:
 
    .. command-output:: buildtest build -b tutorials/test_status/file_count_file_traverse_limit.yml
 
+Status Mode
+~~~~~~~~~~~~~~
+
+By default, the status check performed by buildtest is a logical OR, where if any of the status check is True, then the test will
+PASS. However, if you want to change this behavior to logical AND, you can use the `mode` property. The valid values are
+``any``, ``all``. In the example below, we have two tests that illustrate the use of ``mode``.
+
+.. literalinclude:: ../tutorials/test_status/mode.yml
+   :language: yaml
+   :emphasize-lines: 10,24,25-28
+
+The first test uses ``mode: all`` which implies all status check are evaluated as logical AND, we expect this test to PASS.
+In the second test, we use ``mode: any`` where status check are evalued as logical OR which is the default behavior. Note if ``mode``
+is not specified, it is equivalent to ``mode: any``. In second test, we expect this to pass because **regex** check will PASS however,
+the **returncode** check will fail due to mismatch in returncode. If we changed this to ``mode: all`` then we would expect this test
+to fail.
+
+Shown below is the output of running this test.
+
+.. dropdown:: ``buildtest build -b tutorials/test_status/mode.yml``
+
+   .. command-output:: buildtest build -b tutorials/test_status/mode.yml
+
 Skipping test
 -------------
 
diff --git a/docs/buildspecs/spack.rst b/docs/buildspecs/spack.rst
index ab4b71c1e..2fe473323 100644
--- a/docs/buildspecs/spack.rst
+++ b/docs/buildspecs/spack.rst
@@ -248,40 +248,41 @@ Spack Test
 
 .. Note:: ``spack test`` requires version `0.16.0 <https://github.com/spack/spack/releases/tag/v0.16.0>`_ or higher in order to use this feature.
 
-buildtest can run tests using ``spack test run`` that can be used for testing installed specs with
-tests provided by spack. In order to run tests, you need to declare the ``test`` section
-which is of ``type: object`` in JSON and ``run`` is a required property. The ``run`` section maps to ``spack test run``
+buildtest can run tests via ``spack test run`` that can be used for testing installed specs which comes with builtin tests by the spack framework.
+In order to use this feature, you need to declare the ``test`` section
+which is ``type: object`` in JSON and ``run`` is a required property. The ``run`` section maps to ``spack test run``
 that is responsible for running tests for a list of specs that are specified using the ``specs`` property.
 
 Upon running the tests, we can retrieve results using ``spack test results`` which is configured using the ``results``
-property. The **results** property expects one to specify the ``specs`` or ``suite`` or both in order to retrieve results.
+property. The **results** property can query test results one of the following ways:
 
-The ``suite`` property is used to retrieve test results based on suite name, whereas ``specs`` property can be used to retrieve based
-on spec format. Both properties are a list of string types.
+1. Spec Format: ``spack test results -- <spec>``
+2. Suitename: ``spack test results <suitename>``
 
-In example below we will test `m4` package by running ``spack test run m4`` and report the results with log file.
+
+In example below, we will test **m4** package by running ``spack test run m4`` and specify the `-l` option (i.e `spack test results -l`)
+which will retrieve the test log.
 
 .. literalinclude:: ../../examples/spack/spack_test.yml
     :language: yaml
-    :emphasize-lines: 9-14
+    :emphasize-lines: 9-13
 
-If we look at the generated test, buildtest will install m4 followed by running the test. The **spack test run --alias**
-option is used to reference name of suitename which can be used to reference suitename when using ``spack test results``
-to search for test results. **buildtest will create the suite name based on name of test.** If `--alias` is not specified,
-spack will generate a random text for suitename which you won't know at time of writing test that is required by
-``spack test results`` to fetch the results.
+The **spack test run --alias** option is used to query results by suitename which can be used by ``spack test results`` command.
+**buildtest will create a unique suite name for every run so you don't have to remember the suite name when writing the buildspec.**
 
 .. dropdown:: ``buildtest build -b /home/spack/buildtest/examples/spack/spack_test.yml``
 
     .. program-output:: cat buildtest_tutorial_examples/spack/build/spack_test.txt
 
+Take note of the generated test and the suite-name that is generated by buildtest.
+
 .. dropdown:: ``buildtest inspect query -o -t spack_test_m4``
 
     .. program-output:: cat buildtest_tutorial_examples/spack/inspect/spack_test.txt
 
 
 We can search for test results using the spec format instead of suite name. In the ``results`` property we can
-use ``specs`` field instead of ``suite`` property to specify a list of spec names to run. In spack, you can retrieve
+use ``specs`` field to specify a list of spec names to run. In spack, you can retrieve
 the results using ``spack test results -- <spec>``, note that double dash ``--`` is in front of spec name. We can
 pass options to ``spack test results`` using the **option** property which is available for ``results`` and
 ``run`` property. Currently, spack will write test results in ``$HOME/.spack/tests`` and we can use ``spack test remove``
diff --git a/docs/conferences.rst b/docs/conferences.rst
index e9df0fa24..ee2b52804 100644
--- a/docs/conferences.rst
+++ b/docs/conferences.rst
@@ -10,6 +10,7 @@ Talks
     :header: "Conference", "Talk", "Date", "Link"
     :widths:  40, 50, 20, 20
 
+    "`Improving Scientific Software 2023  <https://sea.ucar.edu/conference/2023>`_", "**Buildtest: A Framework for testing HPC systems**", "Apr 17, 2023", "`PDF <https://drive.google.com/file/d/19UUHtwYVuLrXumR4mn7DI2UvAWNsVRBt/view?usp=share_link>`__,"
     "ECP Project Tutorial", "**Automated Acceptance Testing in HPC with buildtest**", "Feb 7th, 2023", "`PPTX <https://docs.google.com/presentation/d/1zKm-AMLciBwCJOmZdNaCfqP36BAkl2OL/edit?rtpof=true>`_, `PDF <https://drive.google.com/file/d/1CnPH3Ct_YF8H7bLHx0X84TMf3mu_HKBz/view?usp=share_link>`_, `VIDEO <https://youtu.be/YxOm6aiDy3E>`_"
     "HPCSYSPRO_ workshop at SC22_", "`An Automated Approach to Continuous Acceptance Testing of HPC Systems at NERSC <https://sc22.supercomputing.org/session/?sess=sess463>`__", "Nov 14, 2022", "`PDF <https://drive.google.com/file/d/1BLJ0pfMBc3ZTitVIA6VGRDAZdI4gBBul/view?usp=share_link>`__, `PPTX <https://docs.google.com/presentation/d/12hwHxONA_CEBiX3JB3jkqarkVpZnkZTOWsnmhNmGgog/edit?usp=share_link>`__"
     "`PEARC22 <https://pearc.acm.org/pearc22/>`_", "**Testing your HPC System with Buildtest**", "July 11, 2022", "`PDF <https://drive.google.com/file/d/1bbbwCH62M7TSAE-lWecgt7R8myeHYhDV/view?usp=sharing>`__, `PPTX <https://docs.google.com/presentation/d/1nwk7dUhX7qEV75bE_P6lrR5Ion9M8AcG/edit?usp=sharing&ouid=102126664227037583807&rtpof=true&sd=true>`__"
diff --git a/docs/index.rst b/docs/index.rst
index 1d580faed..eb3b19402 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -16,14 +16,11 @@ which references the `devel` branch.
     :target: https://readthedocs.org/projects/buildtest/builds/
 
 .. |license| image:: https://img.shields.io/github/license/buildtesters/buildtest.svg
+    :target: https://github.com/buildtesters/buildtest/blob/devel/LICENSE
 
 .. |codecov| image:: https://codecov.io/gh/buildtesters/buildtest/branch/devel/graph/badge.svg
     :target: https://codecov.io/gh/buildtesters/buildtest
 
-.. |codefactor| image:: https://www.codefactor.io/repository/github/buildtesters/buildtest/badge
-    :target: https://www.codefactor.io/repository/github/buildtesters/buildtest
-    :alt: CodeFactor
-
 .. |regressiontest| image:: https://github.com/buildtesters/buildtest/workflows/regressiontest/badge.svg
     :target: https://github.com/buildtesters/buildtest/actions
 
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 3f0362c4d..28f7f6144 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,3 @@
-codecov
 # issue with rendering bullet points in sphinx docutils 0.17 or higher see https://stackoverflow.com/questions/67542699/readthedocs-sphinx-not-rendering-bullet-list-from-rst-file
 docutils==0.16
 pre-commit
diff --git a/docs/writing_buildspecs/performance_checks.rst b/docs/writing_buildspecs/performance_checks.rst
index 5bbe5550a..a3fb577b4 100644
--- a/docs/writing_buildspecs/performance_checks.rst
+++ b/docs/writing_buildspecs/performance_checks.rst
@@ -9,20 +9,20 @@ Defining Metrics
 ------------------
 
 buildtest provides a method to define test metrics in the buildspecs which can be used to
-store arbitrary content from the output/error file into named metric. A metric is
-defined using the ``metrics`` property where each element under the **metrics** property
-is the name of the metric which must be a unique name. A metric can apply regular expression
-on stdout, stderr like in this example below. The metrics are captured in the test report which can
-be queried via ``buildtest report`` or ``buildtest inspect``. Shown below is an example
-where we define two metrics named ``hpcg_rating`` and ``hpcg_state``.
-
-.. literalinclude:: ../tutorials/metrics_regex.yml
+store arbitrary content from the output/error file or an arbitrary file into named metric. The
+``metrics`` property is used to define a list of metric names using regular expression to assign a value
+to the metric. In this example, we have two tests that define metrics ``hpcg_rate_stream``, ``hpcg_state_stream``
+in the first test and ``hpcg_rate_file``, ``hpcg_state_file`` in the second test. The ``stream`` property is used
+to read from stdout/stderr and apply the regular expression defined by ``exp``, whereas ``file_regex`` is used
+to define metrics from an arbitrary file where ``file`` is the path to file.
+
+.. literalinclude:: ../tutorials/metrics/metrics_regex.yml
     :language: yaml
-    :emphasize-lines: 8-19
+    :emphasize-lines: 8-18,26-36
 
-The metrics will not impact behavior of test, it will only impact the test report. By default
-a metric will be an empty dictionary if there is no ``metrics`` property. If we fail to match
-a regular expression, the metric will be defined as an empty string.
+The metrics can be used with :ref:`comparison_operators` for performing more sophisticated status checks.
+By default, a metric will be an empty dictionary if there is no ``metrics`` property. If we fail to match
+a regular expression, the metric will be defined as an empty string (``''``).
 
 .. Note::
    If your regular expression contains an escape character ``\`` you must surround your
@@ -30,25 +30,73 @@ a regular expression, the metric will be defined as an empty string.
 
 Let's build this test.
 
-.. dropdown:: ``buildtest build -b tutorials/metrics_regex.yml``
+.. dropdown:: ``buildtest build -b tutorials/metrics/metrics_regex.yml``
 
-   .. command-output:: buildtest build -b tutorials/metrics_regex.yml
+   .. command-output:: buildtest build -b tutorials/metrics/metrics_regex.yml
 
-We can query the metrics via ``buildtest report`` which will display all metrics as a comma separted
+The metrics are captured in the test report which can
+be queried via ``buildtest report`` or ``buildtest inspect query``. Metrics can be seen in the test metadata,
+for instance you can run ``buildtest inspect query`` and you will see metrics shown in table output.
+
+.. dropdown:: ``buildtest inspect query metric_regex_example metric_file_regex``
+
+    .. command-output:: buildtest inspect query metric_regex_example metric_file_regex
+
+We can query the metrics via ``buildtest report`` which will display all metrics as a comma seperated
 **Key/Value** pair. We can use ``buildtest report --format metrics`` to extract all metrics for a test.
 Internally, we store the metrics as a dictionary but when we print them out via ``buildtest report`` we
 join them together into a single string. Shown below is the metrics for the previous build.
 
-.. dropdown:: ``buildtest report --filter buildspec=tutorials/metrics_regex.yml --format name,metrics``
+.. dropdown:: ``buildtest report --filter buildspec=tutorials/metrics/metrics_regex.yml --format name,metrics``
+
+   .. command-output:: buildtest report --filter buildspec=tutorials/metrics/metrics_regex.yml --format name,metrics
+
+
+Invalid Metrics
+~~~~~~~~~~~~~~~~~
+
+We will discuss a few edge-cases when defining metrics that can lead to validation error. The `file_regex` and
+`regex` property can't be declared at the same time when defining a metric. In example below
+we have defined a metric named ``hello`` that uses both ``regex`` and ``file_regex``.
 
-   .. command-output:: buildtest report --filter buildspec=tutorials/metrics_regex.yml --format name,metrics
+.. literalinclude:: ../tutorials/metrics/invalid_metrics.yml
+    :language: yaml
+    :emphasize-lines: 10-15
+
+If we try to validate this buildspec, we will get an error message that ``regex`` and ``file_regex`` can't be specified
+at the same time.
+
+.. dropdown:: ``buildtest buildspec validate  -b tutorials/metrics/invalid_metrics.yml``
+   :color: warning
+
+   .. command-output:: buildtest buildspec validate  -b tutorials/metrics/invalid_metrics.yml
+      :returncode: 1
+
+When defining a metrics, you must specify ``regex`` or ``file_regex`` property in order to capture metric. If its not
+specified, you will run into validation error. In this example, we define a metrics named ``foo``, but we don't
+specify the ``regex`` or ``file_regex`` property therefore, this metric is invalid.
+
+.. literalinclude:: ../tutorials/metrics/missing_required_in_metrics.yml
+    :language: yaml
+    :emphasize-lines: 7-9
 
-You can define a metric based on :ref:`variables <variables>` or :ref:`environment variables <environment_variables>`
-which requires you have set ``vars`` or ``env`` property in the buildspec. The ``vars`` and
-``env`` is a property under the metric name that can be used to reference name
-of variable or environment variable. If you reference an invalid name, buildtest will assign the metric an empty string.
-In this next example, we define two metrics ``gflop`` and ``foo`` that are assigned to variable ``GFLOPS`` and
-environment variable ``FOO``.
+The metrics must follow a pattern, this is typically alphanumeric characters including dot (``.``), hypen (``-``)
+and underscore (``_``). In this example below, we have an invalid metric that doesn't conform to pattern.
+
+.. literalinclude:: ../tutorials/metrics/invalid_metric_name.yml
+    :language: yaml
+    :emphasize-lines: 8
+
+Let's try validating the buildspec to see the error message.
+
+.. dropdown:: ``buildtest buildspec validate  -b tutorials/metrics/invalid_metric_name.yml``
+   :color: warning
+
+   .. command-output:: buildtest buildspec validate  -b tutorials/metrics/invalid_metric_name.yml
+      :returncode: 1
+
+
+.. _comparison_operators:
 
 Comparison Operators
 ----------------------
@@ -59,6 +107,7 @@ is compared with a reference value that can be useful when running performance c
 - :ref:`assert_ge`
 - :ref:`assert_gt`
 - :ref:`assert_le`
+- :ref:`assert_lt`
 - :ref:`assert_eq`
 - :ref:`assert_ne`
 - :ref:`assert_range`
@@ -126,6 +175,18 @@ In this example, we perform a **<=** operation, this can be done via ``assert_le
     :emphasize-lines: 37-46
     :linenos:
 
+.. _assert_lt:
+
+Less Than
+~~~~~~~~~~~
+
+In this example, we perform a **<** operation, this can be done via ``assert_lt`` property
+
+.. literalinclude:: ../tutorials/perf_checks/assert_lt.yml
+    :language: yaml
+    :emphasize-lines: 37-46
+    :linenos:
+
 .. _assert_eq:
 
 Assert Equal
diff --git a/scripts/spack_container/doc-examples.py b/scripts/spack_container/doc-examples.py
index d9b8326c9..2ef22832d 100644
--- a/scripts/spack_container/doc-examples.py
+++ b/scripts/spack_container/doc-examples.py
@@ -10,7 +10,6 @@
 from buildtest.utils.file import create_dir, is_dir, is_file
 
 if __name__ == "__main__":
-
     if getpass.getuser() != "spack" or os.getenv("HOME") != "/home/spack":
         sys.exit(
             "This script can only be run inside container: ghcr.io/buildtesters/buildtest_spack:latest"
diff --git a/tutorials/metrics/invalid_metric_name.yml b/tutorials/metrics/invalid_metric_name.yml
new file mode 100644
index 000000000..44b78fd6c
--- /dev/null
+++ b/tutorials/metrics/invalid_metric_name.yml
@@ -0,0 +1,12 @@
+buildspecs:
+  invalid_metrics_name:
+    type: script
+    executor: generic.local.bash
+    description: "Metrics name does not follow pattern"
+    run: echo "hello" > file.txt
+    metrics:
+      (foo-bar:
+        type: str
+        regex:
+          stream: stdout
+          exp: "BAR"
\ No newline at end of file
diff --git a/tutorials/metrics/invalid_metrics.yml b/tutorials/metrics/invalid_metrics.yml
new file mode 100644
index 000000000..501d79ac8
--- /dev/null
+++ b/tutorials/metrics/invalid_metrics.yml
@@ -0,0 +1,15 @@
+buildspecs:
+  metrics_with_regex_and_file_regex_not_allowed:
+    type: script
+    executor: generic.local.bash
+    description: This is an invalid metric because 'file_regex' and 'regex' are both specified
+    run: echo "hello" > file.txt
+    metrics:
+      hello:
+        type: str
+        file_regex:
+          file: file.txt
+          exp: "BAR"
+        regex:
+          stream: stdout
+          exp: "BAR"
diff --git a/tutorials/metrics/metrics_file_regex_invalid_file.yml b/tutorials/metrics/metrics_file_regex_invalid_file.yml
new file mode 100644
index 000000000..d00968be1
--- /dev/null
+++ b/tutorials/metrics/metrics_file_regex_invalid_file.yml
@@ -0,0 +1,13 @@
+buildspecs:
+  metric_file_regex_invalid_file:
+    executor: generic.local.bash
+    type: script
+    description: capture result metric from file path when we have invalid file path
+    run: echo "HPCG result is VALID with a GFLOP/s rating of=63.6515" > hpcg.txt
+    tags: tutorials
+    metrics:
+      hpcg_rating:
+        type: float
+        file_regex:
+          exp: '(\d+\.\d+)$'
+          file: bad_file.txt
\ No newline at end of file
diff --git a/tutorials/metrics/metrics_regex.yml b/tutorials/metrics/metrics_regex.yml
new file mode 100644
index 000000000..555e07fc6
--- /dev/null
+++ b/tutorials/metrics/metrics_regex.yml
@@ -0,0 +1,36 @@
+buildspecs:
+  metric_regex_example:
+    executor: generic.local.bash
+    type: script
+    description: capture result metric from output
+    run: echo "HPCG result is VALID with a GFLOP/s rating of=63.6515"
+    tags: tutorials
+    metrics:
+      hpcg_rating_stream:
+        type: float
+        regex:
+          exp: '(\d+\.\d+)$'
+          stream: stdout
+      hpcg_state_stream:
+        type: str
+        regex:
+          exp: '(VALID)'
+          stream: stdout
+
+  metric_file_regex:
+    executor: generic.local.bash
+    type: script
+    description: capture result metric from file path
+    run: echo "HPCG result is VALID with a GFLOP/s rating of=63.6515" > hpcg.txt
+    tags: tutorials
+    metrics:
+      hpcg_rating_file:
+        type: float
+        file_regex:
+          exp: '(\d+\.\d+)$'
+          file: hpcg.txt
+      hpcg_state_file:
+        type: str
+        file_regex:
+          exp: '(VALID)'
+          file: hpcg.txt
\ No newline at end of file
diff --git a/tutorials/metrics/missing_required_in_metrics.yml b/tutorials/metrics/missing_required_in_metrics.yml
new file mode 100644
index 000000000..ed60fe9e0
--- /dev/null
+++ b/tutorials/metrics/missing_required_in_metrics.yml
@@ -0,0 +1,9 @@
+buildspecs:
+  required_property_in_metrics:
+    type: script
+    executor: generic.local.bash
+    description: metrics must have a file_regex or regex property to define how to capture metric
+    run: echo "hello"
+    metrics:
+      foo:
+        type: str
\ No newline at end of file
diff --git a/tutorials/metrics_regex.yml b/tutorials/metrics_regex.yml
deleted file mode 100644
index 2eb86eaaf..000000000
--- a/tutorials/metrics_regex.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-buildspecs:
-  metric_regex_example:
-    executor: generic.local.sh
-    type: script
-    description: capture result metric from output
-    run: echo "HPCG result is VALID with a GFLOP/s rating of=63.6515"
-    tags: tutorials
-    metrics:
-      hpcg_rating:
-        type: float
-        regex:
-          exp: '(\d+\.\d+)$'
-          stream: stdout
-
-      hpcg_state:
-        type: str
-        regex:
-          exp: '(VALID)'
-          stream: stdout
\ No newline at end of file


Test patch:
diff --git a/buildtest/__init__.py b/buildtest/__init__.py
index caca2e30c..ae377dad7 100644
--- a/buildtest/__init__.py
+++ b/buildtest/__init__.py
@@ -1,3 +1,3 @@
-BUILDTEST_VERSION = "1.3"
+BUILDTEST_VERSION = "1.4"
 __version__ = BUILDTEST_VERSION
 BUILDTEST_COPYRIGHT = "Copyright (c) 2021-2023, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy), Shahzeb Siddiqui, and Vanessa Sochat. All rights reserved."
diff --git a/buildtest/builders/base.py b/buildtest/builders/base.py
index 024ea3847..e6ba6281a 100644
--- a/buildtest/builders/base.py
+++ b/buildtest/builders/base.py
@@ -46,7 +46,13 @@
 from buildtest.scheduler.slurm import SlurmJob
 from buildtest.schemas.defaults import schema_table
 from buildtest.utils.command import BuildTestCommand
-from buildtest.utils.file import create_dir, read_file, write_file
+from buildtest.utils.file import (
+    create_dir,
+    is_file,
+    read_file,
+    resolve_path,
+    write_file,
+)
 from buildtest.utils.shell import Shell, is_csh_shell
 from buildtest.utils.timer import Timer
 from buildtest.utils.tools import deep_get
@@ -225,8 +231,29 @@ def _set_metadata_values(self):
         self.metadata["compiler"] = None
 
         self.metadata["result"] = {"state": "N/A", "returncode": "-1", "runtime": 0}
-        self.metadata["check"] = {"returncode": "N/A", "regex": "N/A", "runtime": "N/A"}
-
+        status_check_names = [
+            "regex",
+            "returncode",
+            "runtime",
+            "file_regex",
+            "slurm_job_state",
+            "pbs_job_state",
+            "lsf_job_state",
+            "assert_ge",
+            "assert_gt",
+            "assert_le",
+            "assert_lt",
+            "assert_eq",
+            "assert_ne",
+            "contains",
+            "not_contains",
+            "is_symlink",
+            "exists",
+            "is_dir",
+            "is_file",
+            "file_count",
+        ]
+        self.metadata["check"] = {name: None for name in status_check_names}
         self.metadata["metrics"] = {}
 
         # used to store job id from batch scheduler
@@ -867,31 +894,52 @@ def add_metrics(self):
         if not self.metrics:
             return
 
-        for key in self.metrics.keys():
-            # default value of metric is empty string
+        for key, metric in self.metrics.items():
+            # Default value of metric is an empty string
+            self.metadata["metrics"][key] = ""
+            regex = metric.get("regex")
+            file_regex = metric.get("file_regex")
             self.metadata["metrics"][key] = ""
 
-            # apply regex on stdout/stderr and assign value to metrics
-            if self.metrics[key].get("regex"):
-                if self.metrics[key]["regex"]["stream"] == "stdout":
-                    content = self._output
-                elif self.metrics[key]["regex"]["stream"] == "stderr":
-                    content = self._error
-
-                pattern = self.metrics[key]["regex"]["exp"]
-                match = re.search(pattern, content)
-
-                group_number = self.metrics[key]["regex"].get("item") or 0
+            if regex:
+                stream = regex.get("stream")
+                content = self._output if stream == "stdout" else self._error
+                match = re.search(regex["exp"], content)
 
-                # if pattern match found we assign value to metric
                 if match:
                     try:
-                        self.metadata["metrics"][key] = match.group(group_number)
+                        self.metadata["metrics"][key] = match.group(
+                            regex.get("item", 0)
+                        )
                     except IndexError:
-                        self.metadata["metrics"][key] = ""
+                        self.logger.error(
+                            f"Unable to fetch match group: {regex.get('item', 0)} for metric: {key}."
+                        )
+                        continue
+            elif file_regex:
+                fname = file_regex["file"]
+                if fname:
+                    resolved_fname = resolve_path(fname)
+                    if not is_file(resolved_fname):
+                        msg = f"[blue]{self}[/]: Unable to resolve file path: {fname} for metric: {key}"
+                        self.logger.error(msg)
+                        console.print(msg, style="red")
+                        continue
+
+                    content = read_file(resolved_fname)
+                    match = re.search(file_regex["exp"], content) if content else None
+
+                    if match:
+                        try:
+                            self.metadata["metrics"][key] = match.group(
+                                file_regex.get("item", 0)
+                            )
+                        except IndexError:
+                            self.logger.error(
+                                f"Unable to fetch match group: {file_regex.get('item', 0)} for metric: {key}."
+                            )
+                            continue
 
-        # convert all metrics to string types
-        for key in self.metadata["metrics"].keys():
             self.metadata["metrics"][key] = str(self.metadata["metrics"][key])
 
     def output(self):
@@ -939,8 +987,6 @@ def post_run_steps(self):
         self.add_metrics()
         self.check_test_state()
 
-        # self.add_metrics()
-
         # mark job is success if it finished all post run steps
         self.complete()
 
@@ -960,128 +1006,92 @@ def check_test_state(self):
 
         # if status is defined in Buildspec, then check for returncode and regex
         if self.status:
-            slurm_job_state_match = False
-            pbs_job_state_match = False
-            lsf_job_state_match = False
-            assert_ge_match = False
-            assert_le_match = False
-            assert_gt_match = False
-            assert_lt_match = False
-            assert_eq_match = False
-            assert_ne_match = False
-            assert_range_match = False
-            assert_contains_match = False
-            assert_notcontains_match = False
-            assert_is_symlink = False
-            assert_exists = False
-            assert_is_dir = False
-            assert_is_file = False
-            file_regex_match = False
-            assert_file_count = False
-
-            # returncode_match is boolean to check if reference returncode matches return code from test
-            returncode_match = returncode_check(self)
-
-            # check regex against output or error stream based on regular expression
-            # defined in status property. Return value is a boolean
-            regex_match = regex_check(self)
-
-            runtime_match = runtime_check(self)
-
-            self.metadata["check"]["regex"] = regex_match
-            self.metadata["check"]["runtime"] = runtime_match
-            self.metadata["check"]["returncode"] = returncode_match
+            # if 'state' property is specified explicitly honor this value regardless of what is calculated
+            if self.status.get("state"):
+                self.metadata["result"]["state"] = self.status["state"]
+                return
+
+            if self.status.get("returncode"):
+                self.metadata["check"]["returncode"] = returncode_check(self)
+
+            # check regex against output or error stream based on regular expression defined in status property. Return value is a boolean
+            if self.status.get("regex"):
+                self.metadata["check"]["regex"] = regex_check(self)
+
+            if self.status.get("runtime"):
+                self.metadata["check"]["runtime"] = runtime_check(self)
 
             if self.status.get("file_regex"):
-                file_regex_match = file_regex_check(self)
+                self.metadata["check"]["file_regex"] = file_regex_check(self)
 
             if self.status.get("slurm_job_state") and isinstance(self.job, SlurmJob):
-                slurm_job_state_match = (
+                self.metadata["check"]["slurm_job_state"] = (
                     self.status["slurm_job_state"] == self.job.state()
                 )
 
             if self.status.get("pbs_job_state") and isinstance(self.job, PBSJob):
-                pbs_job_state_match = self.status["pbs_job_state"] == self.job.state()
+                self.metadata["check"]["pbs_job_state"] = (
+                    self.status["pbs_job_state"] == self.job.state()
+                )
 
             if self.status.get("lsf_job_state") and isinstance(self.job, LSFJob):
-                lsf_job_state_match = self.status["lsf_job_state"] == self.job.state()
+                self.metadata["check"]["lsf_job_state"] = (
+                    self.status["lsf_job_state"] == self.job.state()
+                )
 
             if self.status.get("assert_ge"):
-                assert_ge_match = assert_ge_check(self)
+                self.metadata["check"]["assert_ge"] = assert_ge_check(self)
 
             if self.status.get("assert_le"):
-                assert_le_match = assert_le_check(self)
+                self.metadata["check"]["assert_le"] = assert_le_check(self)
 
             if self.status.get("assert_gt"):
-                assert_gt_match = assert_gt_check(self)
+                self.metadata["check"]["assert_gt"] = assert_gt_check(self)
 
             if self.status.get("assert_lt"):
-                assert_lt_match = assert_lt_check(self)
+                self.metadata["check"]["assert_lt"] = assert_lt_check(self)
 
             if self.status.get("assert_eq"):
-                assert_eq_match = assert_eq_check(self)
+                self.metadata["check"]["assert_eq"] = assert_eq_check(self)
 
             if self.status.get("assert_ne"):
-                assert_ne_match = assert_ne_check(self)
+                self.metadata["check"]["assert_ne"] = assert_ne_check(self)
 
             if self.status.get("assert_range"):
-                assert_range_match = assert_range_check(self)
+                self.metadata["check"]["assert_range"] = assert_range_check(self)
 
             if self.status.get("contains"):
-                assert_contains_match = contains_check(self)
+                self.metadata["check"]["contains"] = contains_check(self)
 
             if self.status.get("not_contains"):
-                assert_notcontains_match = notcontains_check(self)
+                self.metadata["check"]["not_contains"] = notcontains_check(self)
 
             if self.status.get("is_symlink"):
-                assert_is_symlink = is_symlink_check(builder=self)
+                self.metadata["check"]["is_symlink"] = is_symlink_check(builder=self)
 
             if self.status.get("exists"):
-                assert_exists = exists_check(builder=self)
+                self.metadata["check"]["exists"] = exists_check(builder=self)
 
             if self.status.get("is_dir"):
-                assert_is_dir = is_dir_check(builder=self)
+                self.metadata["check"]["is_dir"] = is_dir_check(builder=self)
 
             if self.status.get("is_file"):
-                assert_is_file = is_file_check(builder=self)
+                self.metadata["check"]["is_file"] = is_file_check(builder=self)
 
             if self.status.get("file_count"):
-                assert_file_count = file_count_check(builder=self)
-
-            # if any of checks is True we set the 'state' to PASS
-            state = any(
-                [
-                    returncode_match,
-                    regex_match,
-                    file_regex_match,
-                    slurm_job_state_match,
-                    pbs_job_state_match,
-                    lsf_job_state_match,
-                    runtime_match,
-                    assert_ge_match,
-                    assert_le_match,
-                    assert_gt_match,
-                    assert_lt_match,
-                    assert_eq_match,
-                    assert_ne_match,
-                    assert_range_match,
-                    assert_contains_match,
-                    assert_notcontains_match,
-                    assert_is_symlink,
-                    assert_exists,
-                    assert_is_dir,
-                    assert_is_file,
-                    assert_file_count,
-                ]
-            )
-            if state:
-                self.metadata["result"]["state"] = "PASS"
-            else:
-                self.metadata["result"]["state"] = "FAIL"
+                self.metadata["check"]["file_count"] = file_count_check(builder=self)
 
-            # if 'state' property is specified explicitly honor this value regardless of what is calculated
-            if self.status.get("state"):
-                self.metadata["result"]["state"] = self.status["state"]
+            # filter out any None values from status check
+            status_checks = [
+                value for value in self.metadata["check"].values() if value is not None
+            ]
+
+            state = (
+                all(status_checks)
+                if self.status.get("mode") == "all"
+                else any(status_checks)
+            )
+            self.metadata["result"]["state"] = "PASS" if state else "FAIL"
 
     def _process_compiler_config(self):
         """This method is responsible for setting cc, fc, cxx class variables based
diff --git a/buildtest/builders/spack.py b/buildtest/builders/spack.py
index 6d86b6f33..7561a412f 100644
--- a/buildtest/builders/spack.py
+++ b/buildtest/builders/spack.py
@@ -4,6 +4,7 @@
 """
 
 import os
+import uuid
 
 from buildtest.builders.base import BuilderBase
 from buildtest.exceptions import BuildTestError
@@ -147,8 +148,8 @@ def _spack_test(self, spack_configuration):
             spack_test_cmd.append(spack_configuration["test"]["run"]["option"])
 
         run_specs = spack_configuration["test"]["run"]["specs"]
-
-        spack_test_cmd.append(f"--alias {self.name}")
+        suite_name = str(uuid.uuid4())
+        spack_test_cmd.append(f"--alias {suite_name}")
 
         for spec in run_specs:
             spack_test_cmd.append(spec)
@@ -156,16 +157,13 @@ def _spack_test(self, spack_configuration):
         lines.append(" ".join(spack_test_cmd))
 
         opts = spack_configuration["test"]["results"].get("option") or ""
-        # fetch results using 'spack test results <suite>'
-        if spack_configuration["test"]["results"].get("suite"):
-            for suite in spack_configuration["test"]["results"]["suite"]:
-                lines.append(f"spack test results {opts} {suite}")
 
         # fetch results using 'spack test results -- <spec>'
         if spack_configuration["test"]["results"].get("specs"):
             for spec in spack_configuration["test"]["results"]["specs"]:
                 lines.append(f"spack test results {opts} -- {spec}")
-
+        else:
+            lines.append(f"spack test results {opts} {suite_name}")
         return lines
 
     def _resolve_spack_root(self, path, verify_spack=True):
diff --git a/buildtest/buildsystem/checks.py b/buildtest/buildsystem/checks.py
index aa049846a..dcd867c1d 100644
--- a/buildtest/buildsystem/checks.py
+++ b/buildtest/buildsystem/checks.py
@@ -39,32 +39,26 @@ def returncode_check(builder):
         builder (buildtest.builders.base.BuilderBase): An instance of BuilderBase class used for printing the builder name
     """
 
-    returncode_match = False
-
-    # if 'returncode' field set for 'status' check the returncode if its not set we return False
-    if "returncode" in builder.status.keys():
-        # returncode can be an integer or list of integers
-        buildspec_returncode = builder.status["returncode"]
-
-        # if buildspec returncode field is integer we convert to list for check
-        if isinstance(buildspec_returncode, int):
-            buildspec_returncode = [buildspec_returncode]
-
-        logger.debug("Conducting Return Code check")
-        logger.debug(
-            "Status Return Code: %s   Result Return Code: %s"
-            % (
-                buildspec_returncode,
-                builder.metadata["result"]["returncode"],
-            )
-        )
-        # checks if test returncode matches returncode specified in Buildspec and assign boolean to returncode_match
-        returncode_match = (
-            builder.metadata["result"]["returncode"] in buildspec_returncode
-        )
-        console.print(
-            f"[blue]{builder}[/]: Checking returncode - {builder.metadata['result']['returncode']} is matched in list {buildspec_returncode}"
+    # returncode can be an integer or list of integers
+    buildspec_returncode = builder.status["returncode"]
+
+    # if buildspec returncode field is integer we convert to list for check
+    if isinstance(buildspec_returncode, int):
+        buildspec_returncode = [buildspec_returncode]
+
+    logger.debug("Conducting Return Code check")
+    logger.debug(
+        "Status Return Code: %s   Result Return Code: %s"
+        % (
+            buildspec_returncode,
+            builder.metadata["result"]["returncode"],
         )
+    )
+    # checks if test returncode matches returncode specified in Buildspec and assign boolean to returncode_match
+    returncode_match = builder.metadata["result"]["returncode"] in buildspec_returncode
+    console.print(
+        f"[blue]{builder}[/]: Checking returncode - {builder.metadata['result']['returncode']} is matched in list {buildspec_returncode}"
+    )
 
     return returncode_match
 
@@ -77,9 +71,6 @@ def runtime_check(builder):
         builder (buildtest.builders.base.BuilderBase): An instance of BuilderBase class used for printing the builder name
     """
 
-    if not builder.status.get("runtime"):
-        return False
-
     min_time = builder.status["runtime"].get("min") or 0
     max_time = builder.status["runtime"].get("max")
 
@@ -177,9 +168,6 @@ def regex_check(builder):
         bool: Returns True if their is a regex match otherwise returns False.
     """
 
-    if not builder.status.get("regex"):
-        return False
-
     file_stream = None
     if builder.status["regex"]["stream"] == "stdout":
         logger.debug(
diff --git a/buildtest/cli/__init__.py b/buildtest/cli/__init__.py
index f4221bf69..526de9014 100644
--- a/buildtest/cli/__init__.py
+++ b/buildtest/cli/__init__.py
@@ -223,12 +223,28 @@ def get_parser():
     )
     parser.add_argument("-r", "--report", help="Specify path to test report file")
 
-    parent_parser = argparse.ArgumentParser(add_help=False)
-    parent_parser.add_argument(
-        "--pager", action="store_true", help="Enable PAGING when viewing result"
-    )
     subparsers = parser.add_subparsers(title="COMMANDS", dest="subcommands", metavar="")
 
+    def get_parent_parser():
+        parent_parser = {}
+
+        parent_parser["pager"] = argparse.ArgumentParser(add_help=False)
+        parent_parser["pager"].add_argument(
+            "--pager", action="store_true", help="Enable PAGING when viewing result"
+        )
+
+        parent_parser["file"] = argparse.ArgumentParser(add_help=False)
+        parent_parser["file"].add_argument(
+            "--file", help="Write configuration file to a new file"
+        )
+
+        parent_parser["row-count"] = argparse.ArgumentParser(add_help=False)
+        parent_parser["row-count"].add_argument(
+            "--row-count", action="store_true", help="Show row count as a global option"
+        )
+        return parent_parser
+
+    parent_parser = get_parent_parser()
     build_menu(subparsers)
     buildspec_menu(subparsers, parent_parser)
     config_menu(subparsers, parent_parser)
@@ -418,7 +434,7 @@ def history_menu(subparsers, parent_parser):
 
     Args:
         subparsers (argparse._SubParsersAction): Subparser object to add subparser
-        parent_parser (argparse.ArgumentParser): Parent parser object to add to subparser
+        parent_parser (argparse.ArgumentParser): Parent parser object
     """
 
     history_subcmd = subparsers.add_parser(
@@ -430,7 +446,9 @@ def history_menu(subparsers, parent_parser):
     )
 
     list_parser = history_subparser.add_parser(
-        "list", help="List a summary of all builds", parents=[parent_parser]
+        "list",
+        help="List a summary of all builds",
+        parents=[parent_parser["pager"], parent_parser["row-count"]],
     )
     list_parser.add_argument(
         "-n",
@@ -446,7 +464,9 @@ def history_menu(subparsers, parent_parser):
     )
 
     query = history_subparser.add_parser(
-        "query", help="Query information for a particular build"
+        "query",
+        help="Query information for a particular build",
+        parents=[parent_parser["pager"]],
     )
     query.add_argument("id", type=int, help="Select a build ID")
     query.add_argument(
@@ -668,12 +688,15 @@ def buildspec_menu(subparsers, parent_parser):
         "find",
         aliases=["f"],
         help="Query information from buildspecs cache",
-        parents=[parent_parser],
+        parents=[parent_parser["pager"], parent_parser["row-count"]],
     )
 
     # buildtest buildspec maintainers
     buildspec_maintainers = subparsers_buildspec.add_parser(
-        "maintainers", aliases=["m"], help="Query maintainers from buildspecs cache"
+        "maintainers",
+        aliases=["m"],
+        help="Query maintainers from buildspecs cache",
+        parents=[parent_parser["row-count"]],
     )
 
     subparsers_maintainers = buildspec_maintainers.add_subparsers()
@@ -717,7 +740,7 @@ def buildspec_menu(subparsers, parent_parser):
         metavar="", dest="buildspec_find_subcommand"
     )
     invalid_buildspecs = subparsers_invalid.add_parser(
-        "invalid", help="Show invalid buildspecs"
+        "invalid", help="Show invalid buildspecs", parents=[parent_parser["row-count"]]
     )
 
     # buildtest buildspec find invalid options
@@ -800,11 +823,6 @@ def buildspec_menu(subparsers, parent_parser):
         type=positive_number,
         help="Limit number of entries queried in output",
     )
-    buildspec_find.add_argument(
-        "--row-count",
-        action="store_true",
-        help="Print total count of records from the table.",
-    )
     buildspec_find.add_argument(
         "-r",
         "--rebuild",
@@ -864,7 +882,7 @@ def buildspec_menu(subparsers, parent_parser):
         "summary",
         aliases=["sm"],
         help="Print summary of buildspec cache",
-        parents=[parent_parser],
+        parents=[parent_parser["pager"]],
     )
     # buildtest buildspec validate
     buildspec_validate = subparsers_buildspec.add_parser(
@@ -945,7 +963,10 @@ def config_menu(subparsers, parent_parser):
         "validate", help="Validate buildtest settings file with schema."
     )
     view_parser = subparsers_config.add_parser(
-        "view", aliases=["v"], help="View configuration file", parents=[parent_parser]
+        "view",
+        aliases=["v"],
+        help="View configuration file",
+        parents=[parent_parser["pager"]],
     )
     view_parser.add_argument(
         "-t",
@@ -991,8 +1012,7 @@ def config_menu(subparsers, parent_parser):
     )
 
     compiler_find = subparsers_compiler.add_parser(
-        "find",
-        help="Find compilers",
+        "find", help="Find compilers", parents=[parent_parser["file"]]
     )
     compiler_find.add_argument(
         "-d",
@@ -1032,7 +1052,10 @@ def report_menu(subparsers, parent_parser):
     """
 
     parser_report = subparsers.add_parser(
-        "report", aliases=["rt"], help="Query test report", parents=[parent_parser]
+        "report",
+        aliases=["rt"],
+        help="Query test report",
+        parents=[parent_parser["pager"], parent_parser["row-count"]],
     )
     subparsers = parser_report.add_subparsers(
         description="Fetch test results from report file and print them in table format",
@@ -1045,7 +1068,10 @@ def report_menu(subparsers, parent_parser):
         "path", aliases=["p"], help="Print full path to the report file being used"
     )
     parser_report_summary = subparsers.add_parser(
-        "summary", aliases=["sm"], help="Summarize test report", parents=[parent_parser]
+        "summary",
+        aliases=["sm"],
+        help="Summarize test report",
+        parents=[parent_parser["pager"]],
     )
 
     # buildtest report
@@ -1133,11 +1159,6 @@ def report_menu(subparsers, parent_parser):
         action="store_true",
         help="Print output in machine readable format",
     )
-    parser_report.add_argument(
-        "--row-count",
-        action="store_true",
-        help="Print total count of records from the table.",
-    )
     parser_report_summary.add_argument(
         "--detailed", "-d", action="store_true", help="Enable a more detailed report"
     )
@@ -1164,13 +1185,19 @@ def inspect_menu(subparsers, parent_parser):
         "buildspec",
         aliases=["b"],
         help="Inspect a test based on buildspec",
-        parents=[parent_parser],
+        parents=[parent_parser["pager"]],
     )
     name = subparser.add_parser(
-        "name", aliases=["n"], help="Specify name of test", parents=[parent_parser]
+        "name",
+        aliases=["n"],
+        help="Specify name of test",
+        parents=[parent_parser["pager"]],
     )
     query_list = subparser.add_parser(
-        "query", aliases=["q"], help="Query fields from record"
+        "query",
+        aliases=["q"],
+        help="Query fields from record",
+        parents=[parent_parser["pager"]],
     )
 
     # buildtest inspect buildspec
@@ -1188,7 +1215,7 @@ def inspect_menu(subparsers, parent_parser):
         "list",
         aliases=["l"],
         help="List all test names, ids, and corresponding buildspecs",
-        parents=[parent_parser],
+        parents=[parent_parser["pager"], parent_parser["row-count"]],
     )
     inspect_list.add_argument(
         "-n",
diff --git a/buildtest/cli/build.py b/buildtest/cli/build.py
index aa4f4a931..85a5f0aba 100644
--- a/buildtest/cli/build.py
+++ b/buildtest/cli/build.py
@@ -310,6 +310,12 @@ def discover_buildspecs_by_tags(buildspec_cache, tagnames):
     buildspecs = []
     # query all buildspecs from BUILDSPEC_CACHE_FILE for tags keyword and
     # if it matches input_tag we add buildspec to list
+    tagnames = [
+        tag.strip() for tagname in tagnames for tag in tagname.split(",") if tag.strip()
+    ]
+
+    if not tagnames:
+        return buildspecs, buildspecs_by_tags
 
     for name in tagnames:
         buildspecs_by_tags[name] = set()
diff --git a/buildtest/cli/buildspec.py b/buildtest/cli/buildspec.py
index 02304a12c..da5636468 100644
--- a/buildtest/cli/buildspec.py
+++ b/buildtest/cli/buildspec.py
@@ -80,10 +80,14 @@ def __init__(
 
         self.configuration = configuration
         self.filter = filterfields
-        self.format = formatfields
+        self.format = formatfields or self.configuration.target_config[
+            "buildspecs"
+        ].get("format")
         self.header = header
-        self.pager = pager
-        self.count = count
+        self.pager = pager or self.configuration.target_config.get("pager")
+        self.count = count or self.configuration.target_config["buildspecs"].get(
+            "count"
+        )
         # if --root is not specified we set to empty list instead of None
         self.roots = roots or []
 
@@ -93,10 +97,14 @@ def __init__(
         # stores invalid buildspecs and the error messages
         self.invalid_buildspecs = {}
 
-        self.terse = terse
+        self.terse = terse or self.configuration.target_config["buildspecs"].get(
+            "terse"
+        )
         self.color = checkColor(color)
 
-        self.rebuild = rebuild
+        self.rebuild = rebuild or self.configuration.target_config["buildspecs"].get(
+            "rebuild"
+        )
         self.cache = {}
 
         self.load_paths()
@@ -112,15 +120,17 @@ def get_cache(self):
         return self.cache
 
     def load_paths(self):
-        """Add all paths to search for buildspecs. We must read configuration file
-        and check property ``buildspec_roots`` for list of directories to search.
-        We check all directories exist, if any fail we don't add them to path.
+        """Add all paths to search for buildspecs. We read configuration file
+        and check whether we need to load buildspecs from list of directories.
+        We check if directories exist, if any fail we don't add them to path.
         If no root directories are specified we load the default buildspec roots which are
         `tutorials <https://github.com/buildtesters/buildtest/tree/devel/tutorials>`_
         and `general_tests <https://github.com/buildtesters/buildtest/tree/devel/general_tests>`_ directory.
         """
 
-        buildspec_paths = self.configuration.target_config.get("buildspec_roots") or []
+        buildspec_paths = (
+            self.configuration.target_config["buildspecs"].get("root") or []
+        )
 
         if buildspec_paths:
             self.roots += buildspec_paths
@@ -768,7 +778,7 @@ def print_buildspecs(self, terse=None, header=None, quiet=None, row_count=None):
         """
 
         # Don't print anything if --quiet is set
-        if quiet:
+        if quiet and self.rebuild:
             return
 
         self.terse = terse or self.terse
@@ -904,13 +914,16 @@ def print_maintainers_by_buildspecs(self):
 
         console.print(table)
 
-    def print_invalid_buildspecs(self, error=None, terse=None, header=None):
+    def print_invalid_buildspecs(
+        self, error=None, terse=None, header=None, row_count=None
+    ):
         """Print invalid buildspecs from cache file. This method implements command ``buildtest buildspec find invalid``
 
         Args:
             error (bool, optional): Display error messages for invalid buildspecs. Default is ``False`` where we only print list of invalid buildspecs
             terse (bool, optional): Display output in machine readable format.
             header (bool, optional): Determine whether to print header column in machine readable format.
+            row_count (bool, optional): Display row count of invalid buildspces table
         """
 
         terse = terse or self.terse
@@ -924,6 +937,10 @@ def print_invalid_buildspecs(self, error=None, terse=None, header=None):
             console.print("There are no invalid buildspecs in cache")
             return
 
+        if row_count:
+            print(len(self.cache["invalids"].keys()))
+            return
+
         # implementation for machine readable format specified via --terse
         if terse:
             if not header:
@@ -1122,7 +1139,7 @@ def show_failed_buildspecs(
         report_file (str, optional): Full path to report file to read
         theme (str, optional): Color theme to choose. This is the Pygments style (https://pygments.org/docs/styles/#getting-a-list-of-available-styles) which is specified by ``--theme`` option
     """
-    results = Report(report_file=report_file)
+    results = Report(report_file=report_file, configuration=configuration)
     all_failed_tests = results.get_test_by_state(state="FAIL")
 
     if test_names:
@@ -1310,6 +1327,7 @@ def buildspec_maintainers(
     header=None,
     color=None,
     name=None,
+    row_count=None,
 ):
     """Entry point for ``buildtest buildspec maintainers`` command.
 
@@ -1320,12 +1338,17 @@ def buildspec_maintainers(
         header (bool, optional): If True disable printing of headers
         color (bool, optional): Print output of table with selected color
         name (str, optional): List all buildspecs corresponding to maintainer name. This command is specified via ``buildtest buildspec maintainers find <name>``
+        row_count (bool, opotional): Print row count of the maintainer table. This command is specified via ``buildtest --row-count buildspec maintainers -l``
     """
 
     cache = BuildspecCache(
         configuration=configuration, terse=terse, header=header, color=color
     )
 
+    if row_count:
+        print(len(cache.list_maintainers()))
+        return
+
     if list_maintainers:
         cache.print_maintainer()
 
@@ -1358,7 +1381,7 @@ def buildspec_find(args, configuration):
     )
 
     if args.buildspec_find_subcommand == "invalid":
-        cache.print_invalid_buildspecs(error=args.error)
+        cache.print_invalid_buildspecs(error=args.error, row_count=args.row_count)
         return
 
     # buildtest buildspec find --tags
diff --git a/buildtest/cli/cd.py b/buildtest/cli/cd.py
index 1bb0defa1..7ccb1ff01 100644
--- a/buildtest/cli/cd.py
+++ b/buildtest/cli/cd.py
@@ -4,15 +4,16 @@
 from buildtest.cli.report import Report
 
 
-def change_directory(test):
+def change_directory(test, configuration):
     """Given a test name we will change directory to root of test for last test run. This
     method implements command ``buildtest cd``
 
     Args:
         test (str): Name of test found in test report. The test is specified via ``buildtest cd <test>``
+        configuration (buildtest.config.SiteConfiguration): Instance of SiteConfiguration class
     """
 
-    report = Report()
+    report = Report(configuration=configuration)
 
     builders = report.builder_names()
 
diff --git a/buildtest/cli/compilers.py b/buildtest/cli/compilers.py
index 27275c296..20466180e 100644
--- a/buildtest/cli/compilers.py
+++ b/buildtest/cli/compilers.py
@@ -10,6 +10,7 @@
 from buildtest.defaults import console
 from buildtest.exceptions import BuildTestError, ConfigurationError
 from buildtest.schemas.defaults import custom_validator, schema_table
+from buildtest.utils.file import is_dir, resolve_path
 from buildtest.utils.tools import deep_get
 from lmod.module import Module
 from lmod.spider import Spider
@@ -23,6 +24,7 @@ def compiler_cmd(args, configuration):
             modulepath=args.modulepath,
             detailed=args.detailed,
             update=args.update,
+            filepath=args.file,
         )
         return
 
@@ -116,7 +118,9 @@ def compiler_test(configuration, compiler_names=None):
         console.print(compiler_fail)
 
 
-def compiler_find(configuration, modulepath=None, detailed=None, update=None):
+def compiler_find(
+    configuration, modulepath=None, detailed=None, update=None, filepath=None
+):
     """This method implements ``buildtest config compilers find`` which detects
     new compilers based on module names defined in configuration. If system has
     Lmod we use Lmodule API to detect the compilers. For environment-modules we
@@ -124,9 +128,10 @@ def compiler_find(configuration, modulepath=None, detailed=None, update=None):
 
     Args:
         configuration (buildtest.config.SiteConfiguration): An instance of SiteConfiguration class
-        modulepath (List, optional): An instance of List, a list of directories to search for modules via MODULEPATH to detect compilers
-        detailed (bool, optional): An instance of bool, flag for printing a detailed report.
-        update (bool, optional): An instance of bool, flag for updating configuration file with new compilers
+        modulepath (List, optional): A list of directories to search for modules via MODULEPATH to detect compilers
+        detailed (bool, optional): Flag for printing a detailed report.
+        update (bool, optional): Flag for updating configuration file with new compilers
+        filepath (str, optional): An option to specify an alternative filepath where configuration file will be written
     """
 
     bc = BuildtestCompilers(
@@ -151,6 +156,16 @@ def compiler_find(configuration, modulepath=None, detailed=None, update=None):
     # print out all compilers from existing configuration file
     # run buildtest config compilers find --update to update existing configuration file
 
+    # check for edge case and raise exception when --file specifies a directory path
+    resolved_filepath = None
+    if filepath:
+        resolved_filepath = resolve_path(filepath, exist=False)
+
+        if is_dir(resolved_filepath):
+            raise BuildTestError(
+                f"The file: {resolved_filepath} is a directory, please specify a file path"
+            )
+
     # if --update is specified we update existing configuration file and write backup in same directory
     if update:
         fname = (
@@ -158,7 +173,9 @@ def compiler_find(configuration, modulepath=None, detailed=None, update=None):
             + datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
             + ".yml"
         )
-        backup_file = os.path.join(os.path.dirname(configuration.file), fname)
+        backup_file = resolved_filepath or os.path.join(
+            os.path.dirname(configuration.file), fname
+        )
         copyfile(configuration.file, backup_file)
         print("Writing backup configuration file to: ", backup_file)
         print(f"Updating configuration file: {configuration.file}")
diff --git a/buildtest/cli/help.py b/buildtest/cli/help.py
index 65827120a..96048f847 100644
--- a/buildtest/cli/help.py
+++ b/buildtest/cli/help.py
@@ -25,6 +25,10 @@ def print_build_help():
         "buildtest build -t pass -t python",
         "Build buildspecs by tagname 'pass' and 'python'",
     )
+    table.add_row(
+        "buildtest build -t pass,fail",
+        "Build buildspecs by tagnames that are specified as comma sperated list",
+    )
     table.add_row(
         "buildtest build -e <executor1> -e <executor2>",
         "Building buildspecs by executor",
@@ -155,6 +159,10 @@ def print_buildspec_help():
         "buildtest buildspec find invalid --error",
         "Show invalid buildspecs with error messages",
     )
+    table.add_row(
+        "buildtest buildspec find invalid --row-count",
+        "Print total count of records from the table",
+    )
     console.print(table)
 
     table = Table(title="Validating Buildspecs", show_lines=False)
@@ -222,6 +230,10 @@ def print_buildspec_help():
         "buildtest buildspec maintainers --list",
         "List all maintainers from buildspec cache",
     )
+    table.add_row(
+        "buildtest buildspec maintainers --list --row-count",
+        "Print total count of records from the table",
+    )
     table.add_row(
         "buildtest buildspec maintainers --list --terse --no-header",
         "List all maintainers in machine readable format without header",
@@ -306,6 +318,11 @@ def print_inspect_help():
         "Display all test names, ids, and corresponding buildspec file",
     )
     table.add_row("buildtest inspect list -t", "Show output in terse format")
+    table.add_row("buildtest inspect list --pager", "Paginate output of inspect list")
+    table.add_row(
+        "buildtest inspect list --row-count",
+        "Print total count of records from the table",
+    )
     table.add_row(
         "buildtest inspect name hello", "Display last run for test name 'hello'"
     )
@@ -401,9 +418,20 @@ def print_history_help():
 
     table.add_row("buildtest history list", "List all build history files")
     table.add_row("buildtest history list --terse", "Print output in terse format")
+    table.add_row(
+        "buildtest history list --row-count",
+        "Print total count of records from the table",
+    )
+    table.add_row(
+        "buildtest history list --pager", "Paginate output of the history list"
+    )
     table.add_row(
         "buildtest history query 0", "Query content of history build identifier '0'"
     )
+    table.add_row(
+        "buildtest history query 0 --pager",
+        "Paginate the query content of history build identifier '0'",
+    )
     table.add_row(
         "buildtest history query 0 --log", "Open logfile for build identifier '0'"
     )
diff --git a/buildtest/cli/history.py b/buildtest/cli/history.py
index b8379ef28..13f64d806 100644
--- a/buildtest/cli/history.py
+++ b/buildtest/cli/history.py
@@ -13,7 +13,7 @@
 
 
 def build_history(args):
-    """This is the entry point for command ``buildtest build history`` command which reports
+    """This is the entry point for command ``buildtest history`` command which reports
 
     Args:
         args (dict): Parsed arguments from `ArgumentParser.parse_args <https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.parse_args>`_
@@ -25,10 +25,13 @@ def build_history(args):
             terse=args.terse,
             pager=args.pager,
             color=args.color,
+            row_count=args.row_count,
         )
 
     if args.history == "query":
-        query_builds(build_id=args.id, log_option=args.log, output=args.output)
+        query_builds(
+            build_id=args.id, log_option=args.log, output=args.output, pager=args.pager
+        )
 
 
 def sorted_alphanumeric(data):
@@ -46,7 +49,31 @@ def sorted_alphanumeric(data):
     return sorted(data, key=alphanum_key)
 
 
-def list_build_history(no_header=None, terse=None, pager=None, color=None):
+def print_terse(table, no_header=None, consoleColor=None):
+    """This method prints the buildtest history list in terse mode which is run via command ``buildtest history list --terse``
+
+    Args:
+        table (dict): Table with columns required for the ``buildtest history list`` command.
+        no_header (bool, optional): Control whether header columns are displayed with terse format
+        consoleColor (bool, optional): Select desired color when displaying results
+    """
+
+    row_entry = [table[key] for key in table.keys()]
+    transpose_list = [list(i) for i in zip(*row_entry)]
+    header = "|".join(table.keys())
+
+    # We print the table columns if --no-header is not specified
+    if not no_header:
+        console.print(header, style=consoleColor)
+
+    for row in transpose_list:
+        line = "|".join(row)
+        console.print(f"[{consoleColor}]{line}")
+
+
+def list_build_history(
+    no_header=None, terse=None, pager=None, color=None, row_count=None
+):
     """This method is entry point for ``buildtest history list`` which prints all previous builds
     stored in **BUILD_HISTORY_DIR**. Each directory has a ``build.json`` file that stores content
     of each build that was run by ``buildtest build``.
@@ -56,6 +83,7 @@ def list_build_history(no_header=None, terse=None, pager=None, color=None):
         terse (bool, optional): Print output in terse format
         pager (bool, optional): Print output in paging format
         color (bool, optional): Select desired color when displaying results
+        row_count (bool, optional): Print row count of all previous builds
     """
 
     consoleColor = checkColor(color)
@@ -66,6 +94,10 @@ def list_build_history(no_header=None, terse=None, pager=None, color=None):
     # only filter filters that are 'build.json'
     history_files = [f for f in history_files if os.path.basename(f) == "build.json"]
 
+    if row_count:
+        print(len(history_files))
+        return
+
     # sort all files alpha-numerically
     history_files = sorted_alphanumeric(history_files)
 
@@ -101,20 +133,12 @@ def list_build_history(no_header=None, terse=None, pager=None, color=None):
         table["fail_rate"].append(content["test_summary"]["fail_rate"])
 
     if terse:
-        row_entry = []
-
-        for key in table.keys():
-            row_entry.append(table[key])
-
-        transpose_list = [list(i) for i in zip(*row_entry)]
-
-        # We print the table columns if --no-header is not specified
-        if not no_header:
-            console.print("|".join(table.keys()), style=consoleColor)
+        if pager:
+            with console.pager():
+                print_terse(table, no_header, consoleColor)
+            return
 
-        for row in transpose_list:
-            line = "|".join(row)
-            console.print(f"[{consoleColor}]{line}")
+        print_terse(table, no_header, consoleColor)
         return
 
     history_table = Table(
@@ -170,14 +194,15 @@ def list_build_history(no_header=None, terse=None, pager=None, color=None):
     console.print(history_table)
 
 
-def query_builds(build_id, log_option, output):
+def query_builds(build_id, log_option=None, output=None, pager=None):
     """This method is called when user runs `buildtest history query` which will
     report the build.json and logfile.
 
     Args:
         build_id (int): Build Identifier which is used for querying history file. The indentifier is an integer starting from 0
-        log_option (bool): Option to control whether log file is opened in editor. This is specified via ``buildtest history query -l <id>``
-        output (bool): Display output.txt file which contains output of ``buildtest build`` command. This is passed via ``buildtest history query -o``
+        log_option (bool, optional): Option to control whether log file is opened in editor. This is specified via ``buildtest history query -l <id>``
+        output (bool, optional): Display output.txt file which contains output of ``buildtest build`` command. This is passed via ``buildtest history query -o``
+        pager (bool, optional): Print output in paging format
     """
 
     if not is_dir(BUILD_HISTORY_DIR):
@@ -206,4 +231,9 @@ def query_builds(build_id, log_option, output):
         print(output_content)
         return
 
+    if pager:
+        with console.pager():
+            console.print(content)
+        return
+
     pprint(content)
diff --git a/buildtest/cli/inspect.py b/buildtest/cli/inspect.py
index ad0412272..5782730eb 100644
--- a/buildtest/cli/inspect.py
+++ b/buildtest/cli/inspect.py
@@ -9,40 +9,47 @@
 from buildtest.utils.tools import checkColor
 from rich.pretty import pprint
 from rich.syntax import Syntax
-from rich.table import Column, Table
+from rich.table import Table
 
 
-def inspect_cmd(args, report_file=None):
+def inspect_cmd(args, configuration, report_file=None):
     """Entry point for ``buildtest inspect`` command
 
     Args:
         args (dict): Parsed arguments from `ArgumentParser.parse_args <https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.parse_args>`_
     """
 
-    report = Report(report_file)
+    report = Report(configuration=configuration, report_file=report_file)
 
     # implements command 'buildtest inspect list'
     if args.inspect in ["list", "l"]:
         inspect_list(
             report,
             terse=args.terse,
-            header=args.no_header,
+            no_header=args.no_header,
             builder=args.builder,
             color=args.color,
+            pager=args.pager,
+            row_count=args.row_count,
         )
         return
 
     # implements command 'buildtest inspect name'
     if args.inspect in ["name", "n"]:
-        inspect_by_name(report, args.name)
+        inspect_by_name(report, args.name, args.pager)
         return
 
     if args.inspect in ["query", "q"]:
-        inspect_query(report, args)
+        inspect_query(report, args, args.pager)
         return
 
     if args.inspect in ["buildspec", "b"]:
-        inspect_buildspec(report, input_buildspecs=args.buildspec, all_records=args.all)
+        inspect_buildspec(
+            report,
+            input_buildspecs=args.buildspec,
+            all_records=args.all,
+            pager=args.pager,
+        )
 
 
 def fetch_test_names(report, names):
@@ -88,7 +95,47 @@ def fetch_test_names(report, names):
     return query_builders
 
 
-def inspect_list(report, terse=None, header=None, builder=None, color=None):
+def print_builders(report):
+    """This method prints all the builders.
+
+    Args:
+        report (str): Path to report file
+    """
+
+    builders = report.builder_names()
+    for name in builders:
+        console.print(name)
+
+
+def print_terse(table, no_header=None, console_color=None):
+    """This method prints output of builders in terse mode which is run via command ``buildtest inspect list --terse``
+
+    Args:
+        table (dict): Table with columns required for the ``buildtest inspect list`` command.
+        no_header (bool, optional): Determine whether to print header in terse format.
+        console_color (bool, optional): Select desired color when displaying results
+    """
+
+    row_entry = [table[key] for key in table.keys()]
+    transpose_list = [list(i) for i in zip(*row_entry)]
+
+    # We print the table columns if --no-header is not specified
+    if not no_header:
+        console.print("|".join(table.keys()), style=console_color)
+
+    for row in transpose_list:
+        console.print("|".join(row), style=console_color)
+
+
+def inspect_list(
+    report,
+    terse=None,
+    no_header=None,
+    builder=None,
+    color=None,
+    pager=None,
+    row_count=None,
+):
     """This method list an output of test id, name, and buildspec file from the report cache. The default
     behavior is to display output in table format though this can be changed with terse format which will
     display in parseable format. This method implements command ``buildtest inspect list``
@@ -96,57 +143,78 @@ def inspect_list(report, terse=None, header=None, builder=None, color=None):
     Args:
         report (str): Path to report file
         terse (bool, optional): Print output in terse format
-        header (bool, optional): Determine whether to print header in terse format.
+        no_header (bool, optional): Determine whether to print header in terse format.
         builder (bool, optional): Print output in builder format which can be done via ``buildtest inspect list --builder``
         color (bool, optional): Print table output of ``buildtest inspect list`` with selected color
-
+        pager (bool, optional): Print output in paging format
+        row_count (bool, optional): Print total number of test runs
     """
     consoleColor = checkColor(color)
 
     test_ids = report._testid_lookup()
 
+    if row_count:
+        print(len(test_ids))
+        return
+
     # implement command 'buildtest inspect list --builder'
     if builder:
-        builders = report.builder_names()
-        for name in builders:
-            print(name)
+        if pager:
+            with console.pager():
+                print_builders(report)
+            return
+
+        print_builders(report)
         return
 
+    table = {
+        "id": [],
+        "name": [],
+        "buildspec": [],
+    }
+
+    for identifier in test_ids.keys():
+        table["id"].append(identifier)
+        table["name"].append(test_ids[identifier]["name"])
+        table["buildspec"].append(test_ids[identifier]["buildspec"])
+
     # print output in terse format
     if terse:
-        # print column headers if --no-header is not specified
-        if not header:
-            console.print("[blue]id|name|buildspec")
-
-        for identifier in test_ids.keys():
-            console.print(
-                f"{identifier}|{test_ids[identifier]['name']}|{test_ids[identifier]['buildspec']}"
-            )
+        if pager:
+            with console.pager():
+                print_terse(table, no_header, consoleColor)
+            return
 
+        print_terse(table, no_header, consoleColor)
         return
 
-    table = Table(
-        "[blue]id",
-        "[blue]name",
-        Column(header="[blue]buildspec", overflow="fold"),
-        title="Test Summary by name, id, buildspec",
+    inspect_table = Table(
+        header_style="blue",
+        title="Test Summary by id, name, buildspec",
         row_styles=[consoleColor],
     )
-    for identifier in test_ids.keys():
-        table.add_row(
-            f"{identifier}",
-            f"{test_ids[identifier]['name']}",
-            f"{test_ids[identifier]['buildspec']}",
-        )
-    console.print(table)
+    for column in table.keys():
+        inspect_table.add_column(column)
 
+    for identifier, name, buildspec in zip(
+        table["id"], table["name"], table["buildspec"]
+    ):
+        inspect_table.add_row(identifier, name, buildspec)
 
-def inspect_query(report, args):
-    """Entry point for ``buildtest inspect query`` command.
+    if pager:
+        with console.pager():
+            console.print(inspect_table)
+        return
+
+    console.print(inspect_table)
+
+
+def print_by_query(report, args):
+    """This method prints the test records when they are queried using ``buildtest inspect query`` command.
 
     Args:
-        args (dict): Parsed arguments from `ArgumentParser.parse_args <https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.parse_args>`_
         report (str): Path to report file
+        args (dict): Parsed arguments from `ArgumentParser.parse_args <https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.parse_args>`_
     """
 
     records = {}
@@ -228,13 +296,31 @@ def inspect_query(report, args):
                     console.print(syntax)
 
 
-def inspect_buildspec(report, input_buildspecs, all_records):
+def inspect_query(report, args, pager=None):
+    """Entry point for ``buildtest inspect query`` command.
+
+    Args:
+        report (str): Path to report file
+        args (dict): Parsed arguments from `ArgumentParser.parse_args <https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.parse_args>`_
+        pager (bool, optional): Print output in paging format
+    """
+
+    if pager:
+        with console.pager():
+            print_by_query(report, args)
+        return
+
+    print_by_query(report, args)
+
+
+def inspect_buildspec(report, input_buildspecs, all_records, pager=None):
     """This method implements command ``buildtest inspect buildspec``
 
     Args:
         report (str): Path to report file
         input_buildspecs (list): List of buildspecs to search in report file. This is specified as positional arguments to ``buildtest inspect buildspec``
         all_records (bool): Determine whether to display all records for every test that matches the buildspec. By default we retrieve the latest record.
+        pager (bool, optional): Print output in paging format
     """
 
     search_buildspecs = []
@@ -288,21 +374,16 @@ def inspect_buildspec(report, input_buildspecs, all_records):
 
     records = latest_records or records
 
-    pprint(records)
-
-
-def inspect_by_name(report, names):
-    """Implements command ``buildtest inspect name`` which will print all test records by given name in JSON format.
-
-    .. code-block:: console
+    if pager:
+        with console.pager():
+            console.print(records)
+        return
 
-        # get last run for test exit1_fail
-        buildtest inspect name exit1_fail
+    pprint(records)
 
-    .. code-block:: console
 
-        # get record exit1_fail that starts with id 123
-        buildtest inspect name exit1_fail/123
+def print_by_name(report, names):
+    """This method prints test records by given name in JSON format.
 
     Args:
         report (str): Path to report file
@@ -324,4 +405,32 @@ def inspect_by_name(report, names):
 
         records[name].append(report.fetch_records_by_ids([tid]))
 
-    pprint(records)
+    console.print(records)
+
+
+def inspect_by_name(report, names, pager=None):
+    """Implements command ``buildtest inspect name`` which will print all test records by given name in JSON format.
+
+    .. code-block:: console
+
+        # get last run for test exit1_fail
+        buildtest inspect name exit1_fail
+
+    .. code-block:: console
+
+        # get record exit1_fail that starts with id 123
+        buildtest inspect name exit1_fail/123
+
+    Args:
+        report (str): Path to report file
+        names (list): List of test names to search in report file. This is specified as positional arguments to ``buildtest inspect name``
+        pager (bool, optional): Print output in paging format
+    """
+
+    if pager:
+        with console.pager():
+            print_by_name(report, names)
+        return
+
+    print_by_name(report, names)
+    return
diff --git a/buildtest/cli/path.py b/buildtest/cli/path.py
index 09d64a7cf..3ac8d960a 100644
--- a/buildtest/cli/path.py
+++ b/buildtest/cli/path.py
@@ -5,6 +5,7 @@
 
 def path_cmd(
     name,
+    configuration,
     testpath=None,
     outfile=None,
     errfile=None,
@@ -39,6 +40,7 @@ def path_cmd(
 
     Args:
         name (str): Name of test to search in report file
+        configuration (buildtest.config.SiteConfiguration): Instance of SiteConfiguration class
         testpath (bool): Retrieve path to testpath for a given test
         outfile (bool): Retrieve path output file for a given test
         errfile (bool): Retrieve path to error file for a given test
@@ -46,7 +48,7 @@ def path_cmd(
         stagedir (bool): Retrieve path to stage directory for a given test
         buildenv (bool): Retrieve path to buildenv for a given test
     """
-    report = Report()
+    report = Report(configuration=configuration)
 
     tid = None
     builders = report.builder_names()
diff --git a/buildtest/cli/report.py b/buildtest/cli/report.py
index 5bbfb6c3c..fa8fcad14 100644
--- a/buildtest/cli/report.py
+++ b/buildtest/cli/report.py
@@ -78,6 +78,7 @@ class Report:
 
     def __init__(
         self,
+        configuration,
         report_file=None,
         filter_args=None,
         format_args=None,
@@ -94,6 +95,7 @@ def __init__(
     ):
         """
         Args:
+            configuration (buildtest.config.SiteConfiguration): Instance of SiteConfiguration class that is loaded buildtest configuration.
             report_file (str, optional): Full path to report file to read
             filter_args (str, optional): A comma separated list of Key=Value pair for filter arguments via ``buildtest report --filter``
             format (str, optional): A comma separated list of format fields for altering report table. This is specified via ``buildtest report --format``
@@ -108,14 +110,17 @@ def __init__(
             color (str, optional): An instance of a string class that tells print_report what color the output should be printed in.
 
         """
+        self.configuration = configuration
         self.start = start
         self.end = end
         self.failure = failure
         self.passed = passed
-        self.latest = latest
-        self.oldest = oldest
+        self.latest = latest or self.configuration.target_config["report"].get("latest")
+        self.oldest = oldest or self.configuration.target_config["report"].get("oldest")
         self.filter = filter_args
-        self.format = format_args
+        self.format = format_args or self.configuration.target_config["report"].get(
+            "format"
+        )
         self.pager = pager
         self.color = color
         self.input_report = report_file
@@ -569,6 +574,8 @@ def print_report(
             root_disk_usage|PASS|0
             root_disk_usage|PASS|0
         """
+        count = count or self.configuration.target_config["report"].get("count")
+        terse = terse or self.configuration.target_config["report"].get("terse")
         consoleColor = checkColor(color)
         if terse:
             row_entry = []
@@ -758,10 +765,11 @@ def fetch_records_by_ids(self, testids):
         return records
 
 
-def report_cmd(args, report_file=None):
+def report_cmd(args, configuration, report_file=None):
     """Entry point for ``buildtest report`` command"""
 
     consoleColor = checkColor(args.color)
+    pager = args.pager or configuration.target_config.get("pager")
 
     if args.report_subcommand in ["clear", "c"]:
         # if BUILDTEST_REPORTS file is not present then we have no report files to delete since it tracks all report files that are created
@@ -793,6 +801,7 @@ def report_cmd(args, report_file=None):
         return
 
     results = Report(
+        configuration=configuration,
         filter_args=args.filter,
         format_args=args.format,
         start=args.start,
@@ -810,12 +819,22 @@ def report_cmd(args, report_file=None):
         return
 
     if args.report_subcommand in ["summary", "sm"]:
-        if args.pager:
+        if pager:
             with console.pager():
-                report_summary(results, detailed=args.detailed, color=consoleColor)
+                report_summary(
+                    results,
+                    detailed=args.detailed,
+                    color=consoleColor,
+                    configuration=configuration,
+                )
             return
 
-        report_summary(results, detailed=args.detailed, color=consoleColor)
+        report_summary(
+            results,
+            detailed=args.detailed,
+            color=consoleColor,
+            configuration=configuration,
+        )
         return
 
     if args.helpfilter:
@@ -833,7 +852,8 @@ def report_cmd(args, report_file=None):
     if args.formatfields:
         results.print_raw_format_fields()
         return
-    if args.pager:
+
+    if pager:
         with console.pager():
             results.print_report(
                 terse=args.terse,
@@ -851,7 +871,7 @@ def report_cmd(args, report_file=None):
     )
 
 
-def report_summary(report, detailed=None, color=None):
+def report_summary(report, configuration, detailed=None, color=None):
     """This method will print summary for report file which can be retrieved via ``buildtest report summary`` command
     Args:
         report (buildtest.cli.report.Report): An instance of Report class
@@ -878,12 +898,14 @@ def report_summary(report, detailed=None, color=None):
         filter_args={"state": "PASS"},
         format_args="name,id,executor,state,returncode,runtime",
         report_file=report.reportfile(),
+        configuration=configuration,
     )
 
     fail_results = Report(
         filter_args={"state": "FAIL"},
         format_args="name,id,executor,state,returncode,runtime",
         report_file=report.reportfile(),
+        configuration=configuration,
     )
 
     print_report_summary_output(
diff --git a/buildtest/cli/stats.py b/buildtest/cli/stats.py
index 9693f47cc..b26c5ef6c 100644
--- a/buildtest/cli/stats.py
+++ b/buildtest/cli/stats.py
@@ -4,7 +4,7 @@
 from buildtest.defaults import console
 
 
-def stats_cmd(name, report_file=None):
+def stats_cmd(name, configuration, report_file=None):
     """Entry Point for ``buildtest stats``
 
     Args:
@@ -15,6 +15,7 @@ def stats_cmd(name, report_file=None):
         filter_args={"name": name},
         format_args="name,state,returncode,starttime,endtime,runtime",
         report_file=report_file,
+        configuration=configuration,
     )
 
     first_result = Report(
@@ -22,12 +23,14 @@ def stats_cmd(name, report_file=None):
         format_args="starttime",
         report_file=report_file,
         oldest=True,
+        configuration=configuration,
     )
     last_result = Report(
         filter_args={"name": name},
         format_args="starttime",
         report_file=report_file,
         latest=True,
+        configuration=configuration,
     )
 
     console.print("Total Test Runs: ", len(results.display_table["name"]))
diff --git a/buildtest/main.py b/buildtest/main.py
index 28cfa624b..1f2a36cf0 100644
--- a/buildtest/main.py
+++ b/buildtest/main.py
@@ -226,6 +226,7 @@ def main():
                 header=args.no_header,
                 color=args.color,
                 name=name,
+                row_count=args.row_count,
             )
 
         elif args.buildspecs_subcommand in ["validate", "val"]:
@@ -239,10 +240,10 @@ def main():
 
     # running buildtest inspect
     elif args.subcommands in ["inspect", "it"]:
-        inspect_cmd(args, report_file=report_file)
+        inspect_cmd(args, configuration=configuration, report_file=report_file)
 
     elif args.subcommands in ["stats"]:
-        stats_cmd(name=args.name, report_file=report_file)
+        stats_cmd(name=args.name, configuration=configuration, report_file=report_file)
     # running buildtest config
     elif args.subcommands in ["config", "cg"]:
         #  running buildtest config compilers
@@ -253,7 +254,7 @@ def main():
 
     # buildtest report
     elif args.subcommands in ["report", "rt"]:
-        report_cmd(args, report_file)
+        report_cmd(args, configuration=configuration, report_file=report_file)
 
     elif args.subcommands == "path":
         path_cmd(
@@ -264,6 +265,7 @@ def main():
             buildscript=args.buildscript,
             stagedir=args.stagedir,
             buildenv=args.buildenv,
+            configuration=configuration,
         )
     # running bnuildtest schema
     elif args.subcommands == "schema":
@@ -300,7 +302,7 @@ def main():
         clean(configuration=configuration, yes=args.yes)
 
     elif args.subcommands == "cd":
-        change_directory(args.test)
+        change_directory(args.test, configuration=configuration)
 
     elif args.subcommands == "docs":
         webbrowser.open("https://buildtest.readthedocs.io/")
diff --git a/buildtest/schemas/definitions.schema.json b/buildtest/schemas/definitions.schema.json
index 7935f2c10..8494a1c04 100644
--- a/buildtest/schemas/definitions.schema.json
+++ b/buildtest/schemas/definitions.schema.json
@@ -62,14 +62,39 @@
         }
       }
     },
-    "regex": {
+    "file_regex_in_metrics": {
       "type": "object",
-      "description": "Perform regular expression search using ``re.search`` python module on stdout/stderr stream for reporting if test ``PASS``. ",
+      "additionalProperties": false,
+      "description": "Specify a regular expressions on a filepath used for assigning value to metrics. The regular expression is matched using ``re.search`` python module. ",
       "required": [
-        "stream",
+        "file",
         "exp"
       ],
+      "properties": {
+        "additionalProperties": false,
+        "file": {
+            "type": "string",
+            "description": "Specify a file name to match with regular expression"
+        },
+        "exp": {
+            "type": "string",
+            "description": "Specify a regular expression to run on the selected file name"
+        },
+        "item": {
+          "type": "integer",
+          "minimum": 0,
+          "description": "Specify the item number used to index element in `match.group() <https://docs.python.org/3/library/re.html#match-objects>`_"
+        }
+      }
+    },
+    "regex": {
+      "type": "object",
       "additionalProperties": false,
+      "description": "Perform regular expression search using ``re.search`` python module on stdout/stderr stream for reporting if test ``PASS``. ",
+      "required": [
+        "exp",
+        "stream"
+      ],
       "properties": {
         "stream": {
           "type": "string",
@@ -149,26 +174,39 @@
     "metrics_field": {
       "type": "object",
       "additionalProperties": false,
-      "required": ["type"],
+      "required": [
+        "type"
+      ],
       "properties": {
         "type": {
           "type": "string",
           "description": "Specify python data-type (str, int, float) to convert metric. ",
-          "enum": ["str", "int", "float"]
+          "enum": [
+            "str",
+            "int",
+            "float"
+          ]
         },
         "regex": {
           "$ref": "#/definitions/regex"
+        },
+        "file_regex": {
+          "$ref": "#/definitions/file_regex_in_metrics"
         }
-      }
+      },
+        "oneOf": [
+          { "required": ["regex" ] },
+          { "required": [ "file_regex"] }
+        ]
     },
     "metrics": {
       "type": "object",
       "description": "This field is used for defining one or more metrics that is recorded for each test. A metric must have a unique name which is recorded in the test metadata.",
-      "patternProperties": {
-        "^.*$": {
-          "$ref": "#/definitions/metrics_field",
-          "description": "Name of metric"
-        }
+      "propertyNames": {
+        "pattern": "^[A-Za-z0-9_.-]+$"
+      },
+      "additionalProperties": {
+        "$ref": "#/definitions/metrics_field"
       }
     },
     "state": {
@@ -216,7 +254,6 @@
             "EXIT"
           ]
         },
-
         "returncode": { "$ref": "#/definitions/returncode" },
         "regex": {
           "$ref": "#/definitions/regex",
@@ -535,6 +572,11 @@
         "state": {
           "$ref": "#/definitions/state",
           "description": "explicitly mark state of test regardless of status calculation"
+        },
+        "mode": {
+          "type": "string",
+          "description": "Determine how the status check is resolved, for instance it can be logical AND or OR",
+          "enum": ["any", "all"]
         }
       }
     },
diff --git a/buildtest/schemas/examples/script.schema.json/invalid/examples.yml b/buildtest/schemas/examples/script.schema.json/invalid/examples.yml
index a1d3eb6f8..d6452b7cd 100644
--- a/buildtest/schemas/examples/script.schema.json/invalid/examples.yml
+++ b/buildtest/schemas/examples/script.schema.json/invalid/examples.yml
@@ -59,7 +59,6 @@ buildspecs:
         stream: file
         exp: "world$"
 
-
   regex_additionalProperties_test:
     type: script
     executor: generic.local.bash
@@ -212,6 +211,42 @@ buildspecs:
       foo:
         variable: FOO
 
+  invalid_metrics_name:
+    type: script
+    executor: generic.local.bash
+    description: "Metrics name does not follow pattern"
+    run: echo "hello" > file.txt
+    metrics:
+      (foo-bar:
+        type: str
+        regex:
+          stream: stdout
+          exp: "BAR"
+
+  missing_regex_in_metrics:
+    type: script
+    executor: generic.local.bash
+    description: Test for missing regex in metrics property, which should result in error
+    run: echo "hello"
+    metrics:
+      foo:
+        type: str
+
+  test_regex_and_file_regex_in_metrics:
+    type: script
+    executor: generic.local.bash
+    description: Test for regex and file_regex in metrics. Only one is allowed at a time
+    run: echo "hello" > file.txt
+    metrics:
+      foo:
+        type: str
+        file_regex:
+          file: file.txt
+          exp: "hello"
+        regex:
+          stream: stdout
+          exp: "hello"
+
   invalid_metrics_type:
     type: script
     executor: generic.local.bash
@@ -372,3 +407,11 @@ buildspecs:
         - dir: foo
           count: 1
           file_traverse_limit: 1000000
+  invalid_value_for_mode:
+    type: script
+    executor: generic.local.bash
+    description: The status mode must be 'any' or 'all'
+    run: exit 0
+    status:
+      returncode: 0
+      mode: '1'
diff --git a/buildtest/schemas/examples/script.schema.json/valid/examples.yml b/buildtest/schemas/examples/script.schema.json/valid/examples.yml
index 625786480..ec81d6f2f 100644
--- a/buildtest/schemas/examples/script.schema.json/valid/examples.yml
+++ b/buildtest/schemas/examples/script.schema.json/valid/examples.yml
@@ -273,6 +273,18 @@ buildspecs:
           exp: 'rating of=(\d+\.\d+)$'
           stream: stdout
 
+  metrics_regex_with_file:
+    type: script
+    executor: generic.local.bash
+    description: metrics regular expression example
+    run: echo "HPCG result is VALID with a GFLOP/s rating of=63.6515" > hpcg.txt
+    metrics:
+      hpcg_rating:
+        type: float
+        file_regex:
+          exp: 'rating of=(\d+\.\d+)$'
+          file: hpcg.txt
+
   multi_executor_vars:
     type: script
     executor: 'generic.local.(sh|bash)'
@@ -691,3 +703,31 @@ buildspecs:
         - dir: foo
           count: 10
           file_traverse_limit: 20
+
+  status_logical_and:
+    type: script
+    executor: 'generic.local.bash'
+    description: 'Using logical AND to check status'
+    run: |
+      echo "This is a test"
+      exit 1
+    status:
+      mode: all
+      returncode: 1
+      regex:
+        stream: stdout
+        exp: 'This is a test'
+
+  status_logical_or:
+    type: script
+    executor: 'generic.local.bash'
+    description: 'Using logical OR to check status'
+    run: |
+      echo "This is a test"
+      exit 1
+    status:
+      mode: any
+      returncode: 0
+      regex:
+        stream: stdout
+        exp: 'This is a test'
diff --git a/buildtest/schemas/examples/settings.schema.json/invalid/invalid_executor_names.yml b/buildtest/schemas/examples/settings.schema.json/invalid/invalid_executor_names.yml
new file mode 100644
index 000000000..47085a8d1
--- /dev/null
+++ b/buildtest/schemas/examples/settings.schema.json/invalid/invalid_executor_names.yml
@@ -0,0 +1,32 @@
+system:
+  generic:
+    hostnames: ['.*']
+    moduletool: N/A
+    executors:
+      local:
+        # the line below is invalid due to pattern property
+        ==bash:
+          description: submit jobs on local machine
+          shell: bash -v
+      slurm:
+        # the line below is invalid due to pattern property
+        $haswell:
+          launcher: sbatch
+          options: ["-p haswell", "-t 00:10"]
+      lsf:
+        <>batch:
+          launcher: bsub
+          queue: batch
+          options: ["-q batch", "-t 00:10"]
+      cobalt:
+        <normal>:
+          launcher: qsub
+          queue: normal
+          options: ["-n 1", "-t 10"]
+    compilers:
+      compiler:
+        gcc:
+          default:
+            cc: /usr/bin/gcc
+            cxx: /usr/bin/g++
+            fc: /usr/bin/gfortran
diff --git a/buildtest/schemas/examples/settings.schema.json/valid/cobalt-example.yml b/buildtest/schemas/examples/settings.schema.json/valid/cobalt-example.yml
index c1987d517..8bdf6b5a9 100644
--- a/buildtest/schemas/examples/settings.schema.json/valid/cobalt-example.yml
+++ b/buildtest/schemas/examples/settings.schema.json/valid/cobalt-example.yml
@@ -16,7 +16,25 @@ system:
 
         haswell:
           queue: haswell
+    buildspecs:
+      # whether to rebuild cache file automatically when running `buildtest buildspec find`
+      rebuild: False
+      # limit number of records to display when running `buildtest buildspec find`
+      count: 15
+      # format fields to display when running `buildtest buildspec find`, By default we will show name,description
+      format: "name,description"
+      # enable terse mode
+      terse: False
 
+    report:
+      count: 25
+      #enable terse mode for report
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      # show the latest for every test
+      latest: True
+      # show the oldest for every test
+      oldest: False
     compilers:
       compiler:
         gcc:
diff --git a/buildtest/schemas/examples/settings.schema.json/valid/combined_executor.yml b/buildtest/schemas/examples/settings.schema.json/valid/combined_executor.yml
index 8a6b44fa8..34e6315a5 100644
--- a/buildtest/schemas/examples/settings.schema.json/valid/combined_executor.yml
+++ b/buildtest/schemas/examples/settings.schema.json/valid/combined_executor.yml
@@ -3,6 +3,25 @@ system:
     hostnames: ['.*']
     moduletool: N/A
     poolsize: 1
+    buildspecs:
+      # whether to rebuild cache file automatically when running `buildtest buildspec find`
+      rebuild: False
+      # limit number of records to display when running `buildtest buildspec find`
+      count: 15
+      # format fields to display when running `buildtest buildspec find`, By default we will show name,description
+      format: "name,description"
+      # enable terse mode
+      terse: False
+
+    report:
+      count: 25
+      #enable terse mode for report
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      # show the latest for every test
+      latest: True
+      # show the oldest for every test
+      oldest: False
     executors:
       local:
         bash:
diff --git a/buildtest/schemas/examples/settings.schema.json/valid/local-executor.yml b/buildtest/schemas/examples/settings.schema.json/valid/local-executor.yml
index 5efd24412..a6a808988 100644
--- a/buildtest/schemas/examples/settings.schema.json/valid/local-executor.yml
+++ b/buildtest/schemas/examples/settings.schema.json/valid/local-executor.yml
@@ -15,6 +15,25 @@ system:
       threads_per_core: 2
       sockets: 1
       model: "Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz"
+    buildspecs:
+      # whether to rebuild cache file automatically when running `buildtest buildspec find`
+      rebuild: False
+      # limit number of records to display when running `buildtest buildspec find`
+      count: 15
+      # format fields to display when running `buildtest buildspec find`, By default we will show name,description
+      format: "name,description"
+      # enable terse mode
+      terse: False
+
+    report:
+      count: 25
+      #enable terse mode for report
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      # show the latest for every test
+      latest: True
+      # show the oldest for every test
+      oldest: False
     executors:
       local:
         bash:
diff --git a/buildtest/schemas/examples/settings.schema.json/valid/lsf-example.yml b/buildtest/schemas/examples/settings.schema.json/valid/lsf-example.yml
index 2af44bd29..5ceed7571 100644
--- a/buildtest/schemas/examples/settings.schema.json/valid/lsf-example.yml
+++ b/buildtest/schemas/examples/settings.schema.json/valid/lsf-example.yml
@@ -3,6 +3,25 @@ system:
     hostnames: ['.*']
     moduletool: lmod
     poolsize: 1
+    buildspecs:
+      # whether to rebuild cache file automatically when running `buildtest buildspec find`
+      rebuild: False
+      # limit number of records to display when running `buildtest buildspec find`
+      count: 15
+      # format fields to display when running `buildtest buildspec find`, By default we will show name,description
+      format: "name,description"
+      # enable terse mode
+      terse: False
+
+    report:
+      count: 25
+      #enable terse mode for report
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      # show the latest for every test
+      latest: True
+      # show the oldest for every test
+      oldest: False
     executors:
       defaults:
         pollinterval: 10
diff --git a/buildtest/schemas/examples/settings.schema.json/valid/pbs-example.yml b/buildtest/schemas/examples/settings.schema.json/valid/pbs-example.yml
index 583a5e2b1..e67c67b9b 100644
--- a/buildtest/schemas/examples/settings.schema.json/valid/pbs-example.yml
+++ b/buildtest/schemas/examples/settings.schema.json/valid/pbs-example.yml
@@ -3,6 +3,25 @@ system:
     hostnames: ['.*']
     moduletool: N/A
     poolsize: 1
+    buildspecs:
+      # whether to rebuild cache file automatically when running `buildtest buildspec find`
+      rebuild: False
+      # limit number of records to display when running `buildtest buildspec find`
+      count: 15
+      # format fields to display when running `buildtest buildspec find`, By default we will show name,description
+      format: "name,description"
+      # enable terse mode
+      terse: False
+
+    report:
+      count: 25
+      #enable terse mode for report
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      # show the latest for every test
+      latest: True
+      # show the oldest for every test
+      oldest: False
     executors:
       defaults:
         pollinterval: 10
diff --git a/buildtest/schemas/examples/settings.schema.json/valid/slurm-example.yml b/buildtest/schemas/examples/settings.schema.json/valid/slurm-example.yml
index 1ea2af36b..8d083241a 100644
--- a/buildtest/schemas/examples/settings.schema.json/valid/slurm-example.yml
+++ b/buildtest/schemas/examples/settings.schema.json/valid/slurm-example.yml
@@ -3,8 +3,25 @@ system:
     hostnames: ['.*']
     moduletool: lmod
     poolsize: 1
-    buildspec_roots:
-      - $HOME/buildtest-cori
+    buildspecs:
+      # whether to rebuild cache file automatically when running `buildtest buildspec find`
+      rebuild: False
+      # limit number of records to display when running `buildtest buildspec find`
+      count: 15
+      # format fields to display when running `buildtest buildspec find`, By default we will show name,description
+      format: "name,description"
+      # enable terse mode
+      terse: False
+
+    report:
+      count: 25
+      #enable terse mode for report
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      # show the latest for every test
+      latest: True
+      # show the oldest for every test
+      oldest: False
     testdir: /tmp/buildtest
     executors:
       defaults:
diff --git a/buildtest/schemas/examples/spack.schema.json/invalid/examples.yml b/buildtest/schemas/examples/spack.schema.json/invalid/examples.yml
index 2bc0df56d..6d4454de4 100644
--- a/buildtest/schemas/examples/spack.schema.json/invalid/examples.yml
+++ b/buildtest/schemas/examples/spack.schema.json/invalid/examples.yml
@@ -57,29 +57,6 @@ buildspecs:
         run:
           specs: ['m4', 'zlib']
 
-  spack_test_results_testing_required_oneOf:
-    type: spack
-    executor: generic.local.sh
-    description: "The results property expects one to specify specs, suite, or both"
-    tags: [spack]
-    pre_cmds: |
-      cd /tmp
-      git clone https://github.com/spack/spack
-    spack:
-      root: /tmp/spack
-      verify_spack: false
-      install:
-        specs: ['m4', 'zlib']
-      test:
-        remove_tests: true
-        run:
-          specs: ['m4', 'zlib']
-        results:
-          option: '-f'
-    post_cmds: |
-      spack find
-      rm -rf $SPACK_ROOT
-
   spack_test_run_invalid_spec:
     type: spack
     executor: generic.local.sh
@@ -99,9 +76,6 @@ buildspecs:
           specs: ['m4', 1]
         results:
           option: '-f'
-          suite: ['zlib']
-          specs: ['m4']
-
     post_cmds: |
       spack find
       rm -rf $SPACK_ROOT
diff --git a/buildtest/schemas/examples/spack.schema.json/valid/examples.yml b/buildtest/schemas/examples/spack.schema.json/valid/examples.yml
index d33e94fc4..591042efb 100644
--- a/buildtest/schemas/examples/spack.schema.json/valid/examples.yml
+++ b/buildtest/schemas/examples/spack.schema.json/valid/examples.yml
@@ -143,29 +143,6 @@ buildspecs:
         specs:
           - 'm4'
 
-
-  spack_test_results_specs_suites:
-    type: spack
-    executor: generic.local.sh
-    description: "Report results using suitename and spec format in spack test results"
-    tags: [spack]
-    pre_cmds: |
-      cd /tmp
-      git clone https://github.com/spack/spack
-    spack:
-      root: /tmp/spack
-      verify_spack: false
-      install:
-        specs: ['m4', 'zlib']
-      test:
-        remove_tests: true
-        run:
-          specs: ['m4', 'zlib']
-        results:
-          option: '-f'
-          suite: ['zlib']
-          specs: ['m4']
-
     post_cmds: |
       spack find
       rm -rf $SPACK_ROOT
diff --git a/buildtest/schemas/settings.schema.json b/buildtest/schemas/settings.schema.json
index c6043736c..bc8ebfb70 100644
--- a/buildtest/schemas/settings.schema.json
+++ b/buildtest/schemas/settings.schema.json
@@ -18,12 +18,28 @@
     }
   },
   "definitions": {
+    "terse": {
+      "type": "boolean",
+      "description": "A boolean to determine whether to enable terse mode"
+    },
+    "format": {
+        "type": "string",
+        "description": "Determine the format fields to display when viewing table results"
+    },
+    "count": {
+      "type": "integer",
+      "minimum": 1,
+      "description": "Determine number of records to display in a table"
+    },
+
     "system": {
       "required": [
         "executors",
         "moduletool",
         "hostnames",
-        "compilers"
+        "compilers",
+        "buildspecs",
+        "report"
       ],
       "additionalProperties": false,
       "type": "object",
@@ -45,13 +61,6 @@
           "minimum": 1,
           "description": "Specify size of Process Pool for parallel processing using ``multiprocessing.Pool``"
         },
-        "buildspec_roots": {
-          "type": "array",
-          "items": {
-            "type": "string"
-          },
-          "description": "Specify a list of directory paths to search buildspecs. This field can be used with ``buildtest buildspec find`` to rebuild buildspec cache or build tests using ``buildtest build`` command"
-        },
         "testdir": {
           "type": "string",
           "description": "Specify full path to test directory where buildtest will write tests."
@@ -74,6 +83,46 @@
           "minimum": 1,
           "description": "Specify timeout duration in number of seconds"
         },
+        "pager":
+          {
+            "type": "boolean",
+            "description": "A boolean to determine whether to enable paging when viewing buildspec cache"
+          },
+        "buildspecs":
+        {
+          "type": "object",
+          "description": "Specify configuration for ``buildtest buildspec`` command",
+          "additionalProperties": false,
+          "properties": {
+            "rebuild": {
+              "type": "boolean",
+              "description": "A boolean to determine whether to rebuild buildspec cache"
+            },
+            "count": { "$ref": "#/definitions/count" } ,
+            "format": {
+              "$ref": "#/definitions/format"
+            },
+            "terse": { "$ref": "#/definitions/terse" },
+            "root": {
+              "type": "array",
+              "items": {
+                "type": "string"
+              },
+              "description": "Specify a list of directory paths to search buildspecs. This field can be used with ``buildtest buildspec find`` to rebuild buildspec cache or build tests using ``buildtest build`` command"
+            }
+          }
+        },
+        "report":
+        {
+          "type": "object",
+          "properties": {
+            "terse": { "$ref": "#/definitions/terse" },
+            "format": { "$ref": "#/definitions/format" },
+            "count": { "$ref": "#/definitions/count" },
+            "latest": {"type": "boolean", "description": "A boolean to determine whether to show latest test run"},
+            "oldest": {"type": "boolean", "description": "A boolean to determine whether to show oldest test run"}
+          }
+        },
         "processor": {
           "type": "object",
           "description": "Specify processor information",
@@ -252,46 +301,51 @@
               "local": {
                 "type": "object",
                 "description": "The ``local`` section is used for declaring local executors for running jobs on local machine",
-                "patternProperties": {
-                  "^.*$": {
-                    "$ref": "#/definitions/local"
-                  }
+                "propertyNames": {
+                  "pattern": "^[A-Za-z0-9_.-]+$"
+                },
+                "additionalProperties": {
+                  "$ref": "#/definitions/local"
                 }
               },
               "lsf": {
                 "type": "object",
                 "description": "The ``lsf`` section is used for declaring LSF executors for running jobs using LSF scheduler",
-                "patternProperties": {
-                  "^.*$": {
-                    "$ref": "#/definitions/lsf"
-                  }
+                "propertyNames": {
+                  "pattern": "^[A-Za-z0-9_.-]+$"
+                },
+                "additionalProperties": {
+                  "$ref": "#/definitions/lsf"
                 }
               },
               "slurm": {
                 "type": "object",
                 "description": "The ``slurm`` section is used for declaring Slurm executors for running jobs using Slurm scheduler",
-                "patternProperties": {
-                  "^.*$": {
-                    "$ref": "#/definitions/slurm"
-                  }
+                "propertyNames": {
+                  "pattern": "^[A-Za-z0-9_.-]+$"
+                },
+                "additionalProperties": {
+                  "$ref": "#/definitions/slurm"
                 }
               },
               "cobalt": {
                 "type": "object",
                 "description": "The ``cobalt`` section is used for declaring Cobalt executors for running jobs using Cobalt scheduler",
-                "patternProperties": {
-                  "^.*$": {
-                    "$ref": "#/definitions/cobalt"
-                  }
+                "propertyNames": {
+                  "pattern": "^[A-Za-z0-9_.-]+$"
+                },
+                "additionalProperties": {
+                  "$ref": "#/definitions/cobalt"
                 }
               },
               "pbs": {
                 "type": "object",
                 "description": "The ``pbs`` section is used for declaring PBS executors for running jobs using PBS scheduler",
-                "patternProperties": {
-                  "^.*$": {
-                    "$ref": "#/definitions/pbs"
-                  }
+                "propertyNames": {
+                  "pattern": "^[A-Za-z0-9_.-]+$"
+                },
+                "additionalProperties": {
+                  "$ref": "#/definitions/pbs"
                 }
               }
             }
diff --git a/buildtest/schemas/spack.schema.json b/buildtest/schemas/spack.schema.json
index 42ec6495d..2ab7b1fab 100644
--- a/buildtest/schemas/spack.schema.json
+++ b/buildtest/schemas/spack.schema.json
@@ -210,20 +210,11 @@
           "type": "object",
           "description": "View test results via ``spack test results`` after running tests via ``spack test run``. Results can be viewed using suitename or installed specs or both.",
           "additionalProperties": false,
-          "anyOf": [
-                    {"required": ["specs"] },
-                    {"required": ["suite"] },
-                    {"required": ["specs","suite"] }
-          ],
           "properties": {
             "option": {
               "type": "string",
               "description": "Pass options to ``spack test results``"
             },
-            "suite": {
-                  "$ref": "definitions.schema.json#/definitions/list_of_strings",
-                  "description": "Report results by  suite name by running ``spack test results <suite>``."
-            },
             "specs": {
                   "$ref": "definitions.schema.json#/definitions/list_of_strings",
                   "description": "Report result by spec name by running ``spack test run -- <specs>``."
diff --git a/buildtest/settings/config.yml b/buildtest/settings/config.yml
index 1c4c4a129..f56549b7b 100644
--- a/buildtest/settings/config.yml
+++ b/buildtest/settings/config.yml
@@ -14,6 +14,30 @@ system:
     # pool size for parallel processing using multiprocessing.Pool. Pool Size must be 1 or higher. The pool size is calculated as follows: poolsize = min(poolsize, os.cpu_count())
     poolsize: 1
 
+    # determine whether to enable pagination
+    pager: False
+
+    buildspecs:
+      # whether to rebuild cache file automatically when running `buildtest buildspec find`
+      rebuild: False
+      # limit number of records to display when running `buildtest buildspec find`
+      count: 15
+      # format fields to display when running `buildtest buildspec find`, By default we will show name,description
+      format: "name,description"
+      # enable terse mode
+      terse: False
+      # specify list of directories to search for buildspecs when building cache
+      #root: [ $BUILDTEST_ROOT/examples, /tmp/buildspecs ]
+    report:
+      count: 25
+      #enable terse mode for report
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      # show the latest for every test
+      latest: True
+      # show the oldest for every test
+      oldest: False
+
     executors:
       # define local executors for running jobs locally
       local:
diff --git a/buildtest/settings/spack_container.yml b/buildtest/settings/spack_container.yml
index 578823eb7..705260315 100644
--- a/buildtest/settings/spack_container.yml
+++ b/buildtest/settings/spack_container.yml
@@ -5,7 +5,15 @@ system:
     description: Generic System
     moduletool: lmod
     testdir: "~/runs"
-    buildspec_roots: [$BUILDTEST_ROOT/examples]
+    pager: False
+
+    buildspecs:
+      rebuild: False
+      count: 15
+      formatfields: "name,description"
+      terse: False
+      root: [$BUILDTEST_ROOT/examples]
+
     executors:
       local:
         bash:
diff --git a/docs/buildtest_tutorial_examples/spack/build/spack_test.txt b/docs/buildtest_tutorial_examples/spack/build/spack_test.txt
index eeb2a29a7..c4ef20ea2 100644
--- a/docs/buildtest_tutorial_examples/spack/build/spack_test.txt
+++ b/docs/buildtest_tutorial_examples/spack/build/spack_test.txt
@@ -1,22 +1,22 @@
 $ buildtest build -b /home/spack/buildtest/examples/spack/spack_test.yml 
-╭───────────────────────────────────────────────── buildtest summary ──────────────────────────────────────────────────╮                                                                                                                   
-│                                                                                                                      │                                                                                                                   
-│ User:               spack                                                                                            │                                                                                                                   
-│ Hostname:           f215b09d0d2f                                                                                     │                                                                                                                   
-│ Platform:           Linux                                                                                            │                                                                                                                   
-│ Current Time:       2023/02/06 21:15:51                                                                              │                                                                                                                   
-│ buildtest path:     /home/spack/buildtest/bin/buildtest                                                              │                                                                                                                   
-│ buildtest version:  1.1                                                                                              │                                                                                                                   
-│ python path:        /home/spack/pyenv/buildtest/bin/python3                                                          │                                                                                                                   
-│ python version:     3.8.6                                                                                            │                                                                                                                   
-│ Configuration File: /home/spack/buildtest/buildtest/settings/spack_container.yml                                     │                                                                                                                   
-│ Test Directory:     /home/spack/runs                                                                                 │                                                                                                                   
-│ Report File:        /home/spack/buildtest/var/report.json                                                            │                                                                                                                   
-│ Command:            /home/spack/buildtest/bin/buildtest build -b /home/spack/buildtest/examples/spack/spack_test.yml │                                                                                                                   
-│                                                                                                                      │                                                                                                                   
-╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯                                                                                                                   
-─────────────────────────────────────────────────────────────────────────────────────────────────────────  Discovering Buildspecs ─────────────────────────────────────────────────────────────────────────────────────────────────────────
-                 Discovered buildspecs                 
+╭───────────────────────────────────── buildtest summary ─────────────────────────────────────╮
+│                                                                                             │
+│ User:               spack                                                                   │
+│ Hostname:           94e3cbc65331                                                            │
+│ Platform:           Linux                                                                   │
+│ Current Time:       2023/04/25 13:45:42                                                     │
+│ buildtest path:     /home/spack/buildtest/bin/buildtest                                     │
+│ buildtest version:  1.3                                                                     │
+│ python path:        /home/spack/pyenv/buildtest/bin/python3                                 │
+│ python version:     3.8.6                                                                   │
+│ Configuration File: /home/spack/buildtest/buildtest/settings/spack_container.yml            │
+│ Test Directory:     /home/spack/runs                                                        │
+│ Report File:        /home/spack/buildtest/var/report.json                                   │
+│ Command:            /home/spack/buildtest/bin/buildtest bd -b examples/spack/spack_test.yml │
+│                                                                                             │
+╰─────────────────────────────────────────────────────────────────────────────────────────────╯
+───────────────────────────────────────────────────────────────────────────────────────────────────────  Discovering Buildspecs ────────────────────────────────────────────────────────────────────────────────────────────────────────
+                 Discovered buildspecs
 ╔═════════════════════════════════════════════════════╗
 ║ buildspec                                           ║
 ╟─────────────────────────────────────────────────────╢
@@ -27,37 +27,37 @@ $ buildtest build -b /home/spack/buildtest/examples/spack/spack_test.yml
 Total Discovered Buildspecs:  1
 Total Excluded Buildspecs:  0
 Detected Buildspecs after exclusion:  1
-─────────────────────────────────────────────────────────────────────────────────────────────────────────── Parsing Buildspecs ────────────────────────────────────────────────────────────────────────────────────────────────────────────
+────────────────────────────────────────────────────────────────────────────────────────────────────────── Parsing Buildspecs ──────────────────────────────────────────────────────────────────────────────────────────────────────────
 Valid Buildspecs: 1
 Invalid Buildspecs: 0
 /home/spack/buildtest/examples/spack/spack_test.yml: VALID
 Total builder objects created: 1
-                                                                                  Builders by type=spack                                                                                   
+                                                                                  Builders by type=spack
 ┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
 ┃ builder                ┃ type  ┃ executor           ┃ compiler ┃ nodes ┃ procs ┃ description                                      ┃ buildspecs                                          ┃
 ┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
-│ spack_test_m4/460eadb0 │ spack │ generic.local.bash │ None     │ None  │ None  │ Run spack test for m4 package and report results │ /home/spack/buildtest/examples/spack/spack_test.yml │
+│ spack_test_m4/63314812 │ spack │ generic.local.bash │ None     │ None  │ None  │ Run spack test for m4 package and report results │ /home/spack/buildtest/examples/spack/spack_test.yml │
 └────────────────────────┴───────┴────────────────────┴──────────┴───────┴───────┴──────────────────────────────────────────────────┴─────────────────────────────────────────────────────┘
-────────────────────────────────────────────────────────────────────────────────────────────────────────────── Building Test ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
-spack_test_m4/460eadb0: Creating test directory: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0
-spack_test_m4/460eadb0: Creating the stage directory: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/stage
-spack_test_m4/460eadb0: Writing build script: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/spack_test_m4_build.sh
-────────────────────────────────────────────────────────────────────────────────────────────────────────────── Running Tests ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
+──────────────────────────────────────────────────────────────────────────────────────────────────────────── Building Test ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
+spack_test_m4/63314812: Creating test directory: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812
+spack_test_m4/63314812: Creating the stage directory: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/stage
+spack_test_m4/63314812: Writing build script: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/spack_test_m4_build.sh
+──────────────────────────────────────────────────────────────────────────────────────────────────────────── Running Tests ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Spawning 4 processes for processing builders
-─────────────────────────────────────────────────────────────────────────────────────────────────────────────── Iteration 1 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────
-spack_test_m4/460eadb0 does not have any dependencies adding test to queue
-spack_test_m4/460eadb0: Current Working Directory : /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/stage
-spack_test_m4/460eadb0: Running Test via command: bash --norc --noprofile -eo pipefail spack_test_m4_build.sh
-spack_test_m4/460eadb0: Test completed in 2.1575 seconds
-spack_test_m4/460eadb0: Test completed with returncode: 0
-spack_test_m4/460eadb0: Writing output file -  /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/spack_test_m4.out
-spack_test_m4/460eadb0: Writing error file - /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/spack_test_m4.err
-In this iteration we are going to run the following tests: [spack_test_m4/460eadb0]
-                                                    Test Summary                                                     
+───────────────────────────────────────────────────────────────────────────────────────────────────────────── Iteration 1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
+spack_test_m4/63314812 does not have any dependencies adding test to queue
+In this iteration we are going to run the following tests: [spack_test_m4/63314812]
+spack_test_m4/63314812: Current Working Directory : /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/stage
+spack_test_m4/63314812: Running Test via command: bash --norc --noprofile -eo pipefail spack_test_m4_build.sh
+spack_test_m4/63314812: Test completed in 1.39538 seconds
+spack_test_m4/63314812: Test completed with returncode: 0
+spack_test_m4/63314812: Writing output file -  /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/spack_test_m4.out
+spack_test_m4/63314812: Writing error file - /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/spack_test_m4.err
+                                                    Test Summary
 ┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┓
 ┃ builder                ┃ executor           ┃ status ┃ checks (ReturnCode, Regex, Runtime) ┃ returncode ┃ runtime ┃
 ┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━┩
-│ spack_test_m4/460eadb0 │ generic.local.bash │ PASS   │ N/A N/A N/A                         │ 0          │ 2.1575  │
+│ spack_test_m4/63314812 │ generic.local.bash │ PASS   │ None None None                      │ 0          │ 1.39538 │
 └────────────────────────┴────────────────────┴────────┴─────────────────────────────────────┴────────────┴─────────┘
 
 
@@ -67,4 +67,4 @@ Failed Tests: 0/1 Percentage: 0.000%
 
 
 Adding 1 test results to /home/spack/buildtest/var/report.json
-Writing Logfile to: /home/spack/buildtest/var/logs/buildtest_bpf4x5c3.log
+Writing Logfile to: /home/spack/buildtest/var/logs/buildtest_q357l26y.log
diff --git a/docs/buildtest_tutorial_examples/spack/inspect/spack_test.txt b/docs/buildtest_tutorial_examples/spack/inspect/spack_test.txt
index c5dde4769..163da2425 100644
--- a/docs/buildtest_tutorial_examples/spack/inspect/spack_test.txt
+++ b/docs/buildtest_tutorial_examples/spack/inspect/spack_test.txt
@@ -1,41 +1,41 @@
 $ buildtest inspect query -o -t spack_test_m4 
-─────────────────────────────────────────────────────────────────────────────────────────── spack_test_m4/460eadb0-6966-41db-bc77-1a00af9fe500 ────────────────────────────────────────────────────────────────────────────────────────────
+────────────────────────────────────────────────────────────────────────────────────────── spack_test_m4/63314812-fcd6-4aeb-a51e-c6cb3924d171 ──────────────────────────────────────────────────────────────────────────────────────────
 Executor: generic.local.bash
 Description: Run spack test for m4 package and report results
 State: PASS
 Returncode: 0
-Runtime: 2.1575 sec
-Starttime: 2023/02/06 21:15:51
-Endtime: 2023/02/06 21:15:53
+Runtime: 1.39538 sec
+Starttime: 2023/04/25 13:45:42
+Endtime: 2023/04/25 13:45:44
 Command: bash --norc --noprofile -eo pipefail spack_test_m4_build.sh
-Test Script: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/spack_test_m4.sh
-Build Script: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/spack_test_m4_build.sh
-Output File: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/spack_test_m4.out
-Error File: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/spack_test_m4.err
-Log File: /home/spack/buildtest/var/logs/buildtest_bpf4x5c3.log
-────────────────────────────────────────────────────────────────── Output File: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/spack_test_m4.out ───────────────────────────────────────────────────────────────────
-==> Spack test spack_test_m4                                                                                                                                                                                                               
-==> Testing package m4-1.4.18-mkc3u4x                                                                                                                                                                                                      
-==> Results for test suite 'spack_test_m4':                                                                                                                                                                                                
-==>   m4-1.4.18-mkc3u4x PASSED                                                                                                                                                                                                             
-==> Testing package m4-1.4.18-mkc3u4x                                                                                                                                                                                                      
-==> [2023-02-06-21:15:53.383170] test: ensuring m4 version is 1.4.18                                                                                                                                                                       
-==> [2023-02-06-21:15:53.384286] '/home/spack/spack-workspace/software/linux-ubuntu18.04-x86_64/gcc-7.5.0/m4-1.4.18-mkc3u4x2p2wie6jfhuku7g5rkovcrxps/bin/m4' '--version'                                                                   
-m4 (GNU M4) 1.4.18                                                                                                                                                                                                                         
-Copyright (C) 2016 Free Software Foundation, Inc.                                                                                                                                                                                          
-License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.                                                                                                                                                             
-This is free software: you are free to change and redistribute it.                                                                                                                                                                         
-There is NO WARRANTY, to the extent permitted by law.                                                                                                                                                                                      
-Written by Rene' Seindal.                                                                                                                                                                                                                  
-PASSED                                                                                                                                                                                                                                     
-==> [2023-02-06-21:15:53.392419] test: ensuring m4 example succeeds                                                                                                                                                                        
-==> [2023-02-06-21:15:53.392722] '/home/spack/spack-workspace/software/linux-ubuntu18.04-x86_64/gcc-7.5.0/m4-1.4.18-mkc3u4x2p2wie6jfhuku7g5rkovcrxps/bin/m4' '/home/spack/.spack/test/5mpugnenojhehp5km573ucervxz46odi/m4-1.4.18-mkc3u4x/da
-// macro is defined                                                                                                                                                                                                                        
-Hello, World!                                                                                                                                                                                                                              
-PASSED                                                                                                                                                                                                                                     
-                                                                                                                                                                                                                                           
-──────────────────────────────────────────────────────────────────── Test File: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/460eadb0/spack_test_m4.sh ────────────────────────────────────────────────────────────────────
-#!/bin/bash                                                                                                                                                                                                                                
-source /home/spack/spack/share/spack/setup-env.sh                                                                                                                                                                                          
-spack test run --alias spack_test_m4 m4                                                                                                                                                                                                    
-spack test results -l spack_test_m4                                                                                                                                                                                                        
+Test Script: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/spack_test_m4.sh
+Build Script: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/spack_test_m4_build.sh
+Output File: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/spack_test_m4.out
+Error File: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/spack_test_m4.err
+Log File: /home/spack/buildtest/var/logs/buildtest_q357l26y.log
+───────────────────────────────────────────────────────────────── Output File: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/spack_test_m4.out ─────────────────────────────────────────────────────────────────
+==> Spack test 2f2840ce-3f38-4c2d-afde-9e1065dfbe86
+==> Testing package m4-1.4.18-mkc3u4x
+==> Results for test suite '2f2840ce-3f38-4c2d-afde-9e1065dfbe86':
+==>   m4-1.4.18-mkc3u4x PASSED
+==> Testing package m4-1.4.18-mkc3u4x
+==> [2023-04-25-13:45:43.698955] test: ensuring m4 version is 1.4.18
+==> [2023-04-25-13:45:43.699920] '/home/spack/spack-workspace/software/linux-ubuntu18.04-x86_64/gcc-7.5.0/m4-1.4.18-mkc3u4x2p2wie6jfhuku7g5rkovcrxps/bin/m4' '--version'
+m4 (GNU M4) 1.4.18
+Copyright (C) 2016 Free Software Foundation, Inc.
+License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
+Written by Rene' Seindal.
+PASSED
+==> [2023-04-25-13:45:43.704828] test: ensuring m4 example succeeds
+==> [2023-04-25-13:45:43.705080] '/home/spack/spack-workspace/software/linux-ubuntu18.04-x86_64/gcc-7.5.0/m4-1.4.18-mkc3u4x2p2wie6jfhuku7g5rkovcrxps/bin/m4' '/home/spack/.spack/test/j22esx4avlcbuqpourbkp7wzftm7ilj3/m4-1.4.18-mkc3u4x
+// macro is defined
+Hello, World!
+PASSED
+
+────────────────────────────────────────────────────────────────── Test File: /home/spack/runs/generic.local.bash/spack_test/spack_test_m4/63314812/spack_test_m4.sh ───────────────────────────────────────────────────────────────────
+#!/bin/bash
+source /home/spack/spack/share/spack/setup-env.sh
+spack test run --alias 2f2840ce-3f38-4c2d-afde-9e1065dfbe86 m4
+spack test results -l 2f2840ce-3f38-4c2d-afde-9e1065dfbe86
\ No newline at end of file
diff --git a/docs/configuring_buildtest/overview.rst b/docs/configuring_buildtest/overview.rst
index 7c9c9327c..e0b673f8b 100644
--- a/docs/configuring_buildtest/overview.rst
+++ b/docs/configuring_buildtest/overview.rst
@@ -44,10 +44,7 @@ As you can see the layout of configuration starts with keyword ``system`` which
 used to define one or more systems. Your HPC site may contain more than one cluster,
 so you should define your clusters with meaningful names as this will impact when you
 reference :ref:`executors <configuring_executors>` in buildspecs. In this example, we define one
-cluster called ``generic`` which is a dummy cluster used for running tutorial examples. The
-**required** fields in the system scope are the following::
-
-    "required": ["executors", "moduletool", "hostnames", "compilers"]
+cluster called ``generic`` which is a dummy cluster used for running tutorial examples.
 
 .. _config_hostnames:
 
@@ -118,31 +115,6 @@ configure buildtest to use the module tool. This can be defined via ``moduletool
 
 The `moduletool` property is used for :ref:`detecting compilers <detect_compilers>` when you run ``buildtest config compilers find``.
 
-.. _buildspec_roots:
-
-buildspec roots
------------------
-
-buildtest can discover buildspec using ``buildspec_roots`` keyword. This field is a list
-of directory paths to search for buildspecs. For example we clone the repo
-https://github.com/buildtesters/buildtest-nersc at **$HOME/buildtest-nersc** and assign
-this to **buildspec_roots** as follows:
-
-.. code-block:: yaml
-
-    buildspec_roots:
-      - $HOME/buildtest-nersc
-
-This field is used with the ``buildtest buildspec find`` command. If you rebuild
-your buildspec cache via ``--rebuild`` option, buildtest will search for all buildspecs in
-directories specified by **buildspec_roots** property. buildtest will recursively
-find all **.yml** extension and validate each buildspec with appropriate schema.
-
-By default buildtest will add the ``$BUILDTEST_ROOT/tutorials`` and ``$BUILDTEST_ROOT/general_tests``
-to search path when searching for buildspecs with ``buildtest buildspec find`` command. This
-is only true if there is no root buildspec directory specified which can be done via `buildspec_roots`
-or `--root` option.
-
 .. _configuring_executors:
 
 Configuring Executors
@@ -399,7 +371,7 @@ Alternately, you can override configuration setting via ``buildtest build --acco
 for all batch jobs.
 
 Poll Interval
-----------------
+~~~~~~~~~~~~~~
 
 The ``pollinterval`` field is used  to poll jobs at set interval in seconds
 when job is active in queue. The poll interval can be configured on command line
@@ -411,7 +383,7 @@ using ``buildtest build --pollinterval`` which overrides the configuration value
 
 
 Max Pend Time
----------------
+~~~~~~~~~~~~~~
 
 The ``maxpendtime`` is **maximum** time job can be pending
 within an executor, if it exceeds the limit buildtest will cancel the job.
@@ -441,7 +413,7 @@ For more details on `maxpendtime` click :ref:`here <max_pend_time>`.
 .. _pbs_executors:
 
 PBS Executors
---------------
+~~~~~~~~~~~~~~
 
 .. Note:: buildtest PBS support relies on job history set because buildtest needs to query job after completion using ``qstat -x``. This
           can be configured using ``qmgr`` by setting ``set server job_history_enable=True``. For more details see section **14.15.5.1 Enabling Job History** in `PBS 2021.1.3 Admin Guide <https://help.altair.com/2021.1.3/PBS%20Professional/PBSAdminGuide2021.1.3.pdf>`_
@@ -543,6 +515,76 @@ based on platform (Linux, Mac).
 The buildtest logs will start with **buildtest_** followed by random identifier with
 a **.log** extension.
 
+Configuring Buildspec Cache
+----------------------------
+
+The :ref:`buildtest buildspec find <find_buildspecs>` command can be configured using the configuration file to provide sensible
+defaults. This can be shown in the configuration file below:
+
+.. code-block:: yaml
+
+        buildspecs:
+          # whether to rebuild cache file automatically when running `buildtest buildspec find`
+          rebuild: False
+          # limit number of records to display when running `buildtest buildspec find`
+          count: 15
+          # format fields to display when running `buildtest buildspec find`, By default we will show name,description
+          format: "name,description"
+          # enable terse mode
+          terse: False
+          # specify list of directories to search for buildspecs when building cache
+          #root: [ $BUILDTEST_ROOT/examples, /tmp/buildspecs ]
+
+
+The ``rebuild: False`` means buildtest won't rebuild the buildcache every time you run `buildtest buildspec find`. If the
+cache file is not present, it will automatically rebuild the cache, otherwise it will build the cache if one specifies
+``--rebuild`` option or ``rebuild: True`` is set in the configuration file.
+
+The buildspec cache is built by reading the contents of the buildspec file on the filesystem; therefore if you make changes
+to the buildspecs, you will need to rebuild the buildspec cache by running `buildtest buildspec find --rebuild`.
+
+The configuration options such as ``count``, ``format``, ``terse`` can  be tweaked to your preference. These configuration values
+can be overridden by command line option.
+
+.. _buildspec_roots:
+
+Root directory for searching buildspecs when building the buildspec cache
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In-order to build the buildspec cache, buildtest needs a list of directories to search for buildspecs on the filesystem. This
+can be done by using the ``root`` property where you can specify a list of directories to search. By default, this field is commented out.
+You can specify the paths via ``buildtest buildspec find --root <dir>`` or specify them in the configuration file.
+
+If you rebuild your buildspec cache via ``--rebuild`` option, buildtest will search for all buildspecs in
+directories specified by **root** property. buildtest will recursively find all **.yml** extension and validate
+each buildspec with appropriate schema.
+
+By default buildtest will add the ``$BUILDTEST_ROOT/tutorials`` and ``$BUILDTEST_ROOT/general_tests``
+to search path when searching for buildspecs with ``buildtest buildspec find`` command.
+
+Configuring behavior for buildtest report
+------------------------------------------
+
+The ``report`` section in configuration file allows you to configure behavior of ``buildtest report`` command. The
+``report`` section is shown below:
+
+.. code-block:: yaml
+
+    report:
+      count: 25
+      #enable terse mode for report
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      # show the latest for every test
+      latest: True
+      # show the oldest for every test
+      oldest: False
+
+The ``count`` property limits the number of records to display when running ``buildtest report`` command. The ``format`` property
+controls the fields to display when running ``buildtest report``. The ``terse`` property enables terse mode for ``buildtest report``.
+By default we will show the latest run for each test when running ``buildtest report``. The ``latest`` property can be set to ``False``
+if you want to change this behavior.
+
 .. _cdash_configuration:
 
 CDASH Configuration
diff --git a/docs/gettingstarted/buildingtest.rst b/docs/gettingstarted/buildingtest.rst
index d05e3b1c6..e6de28af8 100644
--- a/docs/gettingstarted/buildingtest.rst
+++ b/docs/gettingstarted/buildingtest.rst
@@ -138,6 +138,13 @@ tags by running ``buildtest buildspec find --tags``.
 .. Note:: The ``--tags`` is used for discovering buildspec file and not filtering tests
    by tag.
 
+You can specify multiple tag names as a comma separated list. In the
+example below we build all tests with tag names ``pass``, ``fail`` and ``network``.
+
+.. dropdown:: ``buildtest build -t pass,fail -t network``
+
+    .. command-output:: buildtest build -t pass,fail -t network
+
 You can combine ``--tags`` with ``--buildspec`` to discover buildspecs in a single command.
 buildtest will query tags and buildspecs independently and combine all discovered
 buildspecs together.
diff --git a/examples/spack/spack_test.yml b/examples/spack/spack_test.yml
index 71d7f70b3..1466bf4b6 100644
--- a/examples/spack/spack_test.yml
+++ b/examples/spack/spack_test.yml
@@ -10,5 +10,4 @@ buildspecs:
         run:
           specs: ['m4']
         results:
-          suite: ['spack_test_m4']
           option: "-l"
\ No newline at end of file
diff --git a/tests/cli/configuration/invalid_executors.yml b/tests/cli/configuration/invalid_executors.yml
index dcffa86e1..8c85faeb5 100644
--- a/tests/cli/configuration/invalid_executors.yml
+++ b/tests/cli/configuration/invalid_executors.yml
@@ -12,7 +12,17 @@ system:
       numcpus: 8
       threads_per_core: 2
       model: "Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz"
-
+    buildspecs:
+      rebuild: False
+      count: 15
+      format: "name,description"
+      terse: False
+    report:
+      count: 25
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      latest: True
+      oldest: False
     executors:
       local:
         bash:
diff --git a/tests/cli/test_build.py b/tests/cli/test_build.py
index f7778cfc7..25ec22b67 100644
--- a/tests/cli/test_build.py
+++ b/tests/cli/test_build.py
@@ -46,6 +46,29 @@ def test_build_by_tags():
     )
     cmd.build()
 
+    #  testing multiple tags as comma seperated list:  buildtest build --tags fail,python --tags network
+    cmd = BuildTest(
+        configuration=configuration,
+        tags=["fail,python", "network"],
+        buildtest_system=system,
+    )
+    cmd.build()
+
+    cmd = BuildTest(
+        configuration=configuration,
+        tags=[",,"],
+        buildtest_system=system,
+    )
+    with pytest.raises(SystemExit):
+        cmd.build()
+
+    cmd = BuildTest(
+        configuration=configuration,
+        tags=["  ,python,  fail,   ,,"],
+        buildtest_system=system,
+    )
+    cmd.build()
+
 
 @pytest.mark.cli
 def test_build_rerun():
@@ -180,6 +203,24 @@ def test_build_buildspecs():
         cmd.build()
 
 
+def test_run_metrics():
+    system = BuildTestSystem()
+    cmd = BuildTest(
+        configuration=configuration,
+        buildspecs=[
+            os.path.join(BUILDTEST_ROOT, "tutorials", "metrics", "metrics_regex.yml"),
+            os.path.join(
+                BUILDTEST_ROOT,
+                "tutorials",
+                "metrics",
+                "metrics_file_regex_invalid_file.yml",
+            ),
+        ],
+        buildtest_system=system,
+    )
+    cmd.build()
+
+
 def test_run_all_perf_checks():
     system = BuildTestSystem()
     buildspecs = walk_tree(
diff --git a/tests/cli/test_buildspec.py b/tests/cli/test_buildspec.py
index d59c9dd15..ea1f3756f 100644
--- a/tests/cli/test_buildspec.py
+++ b/tests/cli/test_buildspec.py
@@ -193,6 +193,7 @@ def test_buildspec_maintainers():
         header=True,
         color=Color.default().name,
     )
+    buildspec_maintainers(configuration=configuration, row_count=True)
     buildspec_maintainers(configuration=configuration, name="@shahzebsiddiqui")
 
 
@@ -201,35 +202,27 @@ def test_buildspec_find_invalid():
     cache = BuildspecCache(configuration=configuration)
 
     # testing buildtest buildspec find invalid. This will assert SystemExit exception raised by sys.exit
-    try:
+    with pytest.raises(SystemExit):
         cache.print_invalid_buildspecs(error=True)
-    except SystemExit:
-        pass
 
-    try:
+    with pytest.raises(SystemExit):
         cache.print_invalid_buildspecs(error=False)
-    except SystemExit:
-        pass
 
-    try:
+    with pytest.raises(SystemExit):
         cache.print_invalid_buildspecs(error=True, terse=True)
-    except SystemExit:
-        pass
 
-    try:
+    with pytest.raises(SystemExit):
         cache.print_invalid_buildspecs(error=False, terse=True)
-    except SystemExit:
-        pass
 
-    try:
+    with pytest.raises(SystemExit):
         cache.print_invalid_buildspecs(error=True, terse=True, header=True)
-    except SystemExit:
-        pass
 
-    try:
+    with pytest.raises(SystemExit):
         cache.print_invalid_buildspecs(error=False, terse=True, header=True)
-    except SystemExit:
-        pass
+
+    cache.print_invalid_buildspecs(
+        error=False, terse=False, header=False, row_count=True
+    )
 
 
 @pytest.mark.cli
@@ -370,11 +363,11 @@ def test_buildspec_show_fail():
 
     # Query a test that is NOT in state=FAIL
 
-    results = Report()
+    results = Report(configuration=configuration)
     pass_test = random.sample(results.get_test_by_state(state="PASS"), 1)
     show_failed_buildspecs(configuration=configuration, test_names=[pass_test])
 
-    report = Report()
+    report = Report(configuration=configuration)
     # get a random failed test from report file to be used for showing content of buildspec file
     fail_tests = random.sample(report.get_test_by_state(state="FAIL"), 1)
     # buildtest buildspec show-fail <test> --theme monokai
diff --git a/tests/cli/test_compilers/invalid_moduletool.yml b/tests/cli/test_compilers/invalid_moduletool.yml
index afd4d6c73..184af0f4a 100644
--- a/tests/cli/test_compilers/invalid_moduletool.yml
+++ b/tests/cli/test_compilers/invalid_moduletool.yml
@@ -2,20 +2,28 @@ system:
   generic:
     hostnames: [".*"]
     moduletool: N/A
+    buildspecs:
+      rebuild: False
+      count: 15
+      format: "name,description"
+      terse: False
+    report:
+      count: 25
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      latest: True
+      oldest: False
     executors:
       local:
         bash:
           description: submit jobs on local machine using bash shell
           shell: bash
-
         sh:
           description: submit jobs on local machine using sh shell
           shell: sh
-
         csh:
           description: submit jobs on local machine using csh shell
           shell: csh
-
         python:
           description: submit jobs on local machine using python shell
           shell: python
diff --git a/tests/cli/test_compilers/missing_compiler_find.yml b/tests/cli/test_compilers/missing_compiler_find.yml
index 227996d41..bfce6c8a8 100644
--- a/tests/cli/test_compilers/missing_compiler_find.yml
+++ b/tests/cli/test_compilers/missing_compiler_find.yml
@@ -2,20 +2,28 @@ system:
   generic:
     hostnames: [".*"]
     moduletool: lmod
+    buildspecs:
+      rebuild: False
+      count: 15
+      format: "name,description"
+      terse: False
+    report:
+      count: 25
+      terse: False
+      format: "name,id,state,runtime,returncode"
+      latest: True
+      oldest: False
     executors:
       local:
         bash:
           description: submit jobs on local machine using bash shell
           shell: bash
-
         sh:
           description: submit jobs on local machine using sh shell
           shell: sh
-
         csh:
           description: submit jobs on local machine using csh shell
           shell: csh
-
         python:
           description: submit jobs on local machine using python shell
           shell: python
diff --git a/tests/cli/test_history.py b/tests/cli/test_history.py
index f2bcb59cb..994cf51aa 100644
--- a/tests/cli/test_history.py
+++ b/tests/cli/test_history.py
@@ -11,23 +11,26 @@
 
 
 def test_build_history_list():
-    # Testing command:  buildtest history list
+    # buildtest history list
     list_build_history(terse=False, no_header=False, pager=False)
 
-    # test with a color: buildtest history list --color <color>
+    # buildtest history list --color <color>
     list_build_history(
         terse=False, no_header=False, pager=False, color=Color.default().name
     )
 
-    # test with pager support: buildtest history list --pager
+    # buildtest history list --pager
     list_build_history(terse=False, no_header=False, pager=True)
 
-    # test with terse mode and with color: buildtest --color <Color> history list --terse
+    # buildtest history list --row-count
+    list_build_history(terse=False, no_header=False, pager=False, row_count=True)
+
+    # buildtest --color <Color> history list --terse --pager
     list_build_history(
-        terse=True, no_header=False, pager=False, color=Color.default().name
+        terse=True, no_header=False, pager=True, color=Color.default().name
     )
 
-    # test with terse and no header: buildtest history list --terse --no-header
+    # buildtest history list --terse --no-header
     list_build_history(terse=True, no_header=True, pager=False)
 
 
@@ -54,13 +57,16 @@ def test_build_history_query():
     build_id = list(range(len(os.listdir(BUILD_HISTORY_DIR))))[-1]
     print(build_id)
 
-    # run buildtest history query <id>
+    # buildtest history query <id>
     query_builds(build_id=build_id, log_option=False, output=False)
 
-    # run buildtest history query <id> --output
+    # buildtest history query <id> --pager
+    query_builds(build_id=build_id, log_option=False, output=False, pager=True)
+
+    # buildtest history query <id> --output
     query_builds(build_id=build_id, log_option=False, output=True)
 
-    # run buildtest history query <id> --log
+    # buildtest history query <id> --log
     query_builds(build_id=build_id, log_option=True, output=False)
 
 
diff --git a/tests/cli/test_inspect.py b/tests/cli/test_inspect.py
index 64f8a872c..373187351 100644
--- a/tests/cli/test_inspect.py
+++ b/tests/cli/test_inspect.py
@@ -6,12 +6,43 @@
 import pytest
 from buildtest.cli.inspect import inspect_cmd
 from buildtest.cli.report import Report
+from buildtest.config import SiteConfiguration
 from buildtest.defaults import BUILDTEST_ROOT
 from rich.color import Color
 
+configuration = SiteConfiguration()
+configuration.detect_system()
+configuration.validate()
+
 
 def test_buildtest_inspect_list():
-    # running buildtest inspect list
+    # buildtest inspect list
+    class args:
+        subcommands = "inspect"
+        inspect = "list"
+        terse = False
+        no_header = False
+        builder = False
+        color = Color.default().name
+        pager = False
+        row_count = False
+
+    inspect_cmd(args, configuration=configuration)
+
+    # buildtest inspect list --pager
+    class args:
+        subcommands = "inspect"
+        inspect = "list"
+        terse = False
+        no_header = False
+        builder = False
+        color = Color.default().name
+        pager = True
+        row_count = False
+
+    inspect_cmd(args, configuration=configuration)
+
+    # buildtest inspect list --row-count
     class args:
         subcommands = "inspect"
         inspect = "list"
@@ -19,10 +50,12 @@ class args:
         no_header = False
         builder = False
         color = Color.default().name
+        pager = False
+        row_count = True
 
-    inspect_cmd(args)
+    inspect_cmd(args, configuration=configuration)
 
-    # running buildtest inspect list --terse --no-header
+    # buildtest inspect list --terse --no-header
     class args:
         subcommands = "inspect"
         inspect = "list"
@@ -30,10 +63,12 @@ class args:
         no_header = True
         builder = False
         color = False
+        pager = False
+        row_count = False
 
-    inspect_cmd(args)
+    inspect_cmd(args, configuration=configuration)
 
-    # running buildtest inspect list --terse
+    # buildtest inspect list --terse --pager
     class args:
         subcommands = "inspect"
         inspect = "list"
@@ -41,10 +76,25 @@ class args:
         no_header = False
         builder = False
         color = False
+        pager = True
+        row_count = False
+
+    inspect_cmd(args, configuration=configuration)
+
+    # buildtest inspect list --builder
+    class args:
+        subcommands = "inspect"
+        inspect = "list"
+        terse = False
+        no_header = False
+        builder = True
+        color = False
+        pager = False
+        row_count = False
 
-    inspect_cmd(args)
+    inspect_cmd(args, configuration=configuration)
 
-    # running buildtest inspect list --builder
+    # buildtest inspect list --builder --pager
     class args:
         subcommands = "inspect"
         inspect = "list"
@@ -52,12 +102,14 @@ class args:
         no_header = False
         builder = True
         color = False
+        pager = True
+        row_count = False
 
-    inspect_cmd(args)
+    inspect_cmd(args, configuration=configuration)
 
 
 def test_buildtest_inspect_name():
-    r = Report()
+    r = Report(configuration=configuration)
 
     # select a random test name
     test_names = r.get_random_tests(num_items=2)
@@ -67,10 +119,11 @@ class args:
         inspect = "name"
         name = test_names
         report = None
+        pager = True
 
     print(f"Querying test names: {args.name}")
     # buildtest inspect name <name1> <name2>
-    inspect_cmd(args)
+    inspect_cmd(args, configuration=configuration)
 
     random_test = [
         "".join(random.choices(string.ascii_letters, k=10)),
@@ -82,18 +135,20 @@ class args:
         inspect = "name"
         name = random_test
         report = None
+        pager = False
 
     print(f"Querying test names: {args.name}")
     with pytest.raises(SystemExit):
-        inspect_cmd(args)
+        inspect_cmd(args, configuration=configuration)
 
     class args:
         subcommands = "inspect"
         inspect = "name"
         name = [r.builder_names()[0]]
         report = None
+        pager = False
 
-    inspect_cmd(args)
+    inspect_cmd(args, configuration=configuration)
 
 
 def test_buildspec_inspect_buildspec():
@@ -105,16 +160,17 @@ class args:
         buildspec = [tf.name]
         report = None
         all = None
+        pager = False
 
     # if buildspec not in cache we raise error
     with pytest.raises(SystemExit):
-        inspect_cmd(args)
+        inspect_cmd(args, configuration=configuration)
 
     # delete file
     tf.close()
     # invalid filepath will raise an error
     with pytest.raises(SystemExit):
-        inspect_cmd(args)
+        inspect_cmd(args, configuration=configuration)
 
     search_buildspec = [
         os.path.join(BUILDTEST_ROOT, "tutorials", "vars.yml"),
@@ -127,9 +183,10 @@ class args:
         buildspec = search_buildspec
         report = None
         all = False
+        pager = False
 
-    # run buildtest inspect buildspec $BUILDTEST_ROOT/tutorials/vars.yml $BUILDTEST_ROOT/tutorials/pass_returncode.yml
-    inspect_cmd(args)
+    # buildtest inspect buildspec $BUILDTEST_ROOT/tutorials/vars.yml $BUILDTEST_ROOT/tutorials/pass_returncode.yml
+    inspect_cmd(args, configuration=configuration)
 
     class args:
         subcommands = "inspect"
@@ -137,13 +194,14 @@ class args:
         buildspec = search_buildspec
         report = None
         all = True
+        pager = True
 
-    # run buildtest inspect buildspec --all $BUILDTEST_ROOT/tutorials/vars.yml $BUILDTEST_ROOT/tutorials/pass_returncode.yml
-    inspect_cmd(args)
+    # buildtest inspect buildspec --all --pager $BUILDTEST_ROOT/tutorials/vars.yml $BUILDTEST_ROOT/tutorials/pass_returncode.yml
+    inspect_cmd(args, configuration=configuration)
 
 
 def test_buildtest_query():
-    report = Report()
+    report = Report(configuration=configuration)
     names = report.get_names()
 
     class args:
@@ -156,9 +214,10 @@ class args:
         buildscript = True
         buildenv = True
         theme = "emacs"
+        pager = True
 
-    # check buildtest inspect query --output --error --testpath --buildscript --buildenv <name1> <name2> ...
-    inspect_cmd(args)
+    # buildtest inspect query --output --error --testpath --buildscript --buildenv <name1> <name2> ...
+    inspect_cmd(args, configuration=configuration)
 
     class args:
         subcommands = "inspect"
@@ -170,10 +229,11 @@ class args:
         buildscript = False
         buildenv = False
         theme = None
+        pager = False
 
     # buildtest inspect query stream_test
     # the 'stream_test' will add coverage where metrics are printed in output of 'buildtest inspect query'
-    inspect_cmd(args)
+    inspect_cmd(args, configuration=configuration)
 
     class args:
         subcommands = "inspect"
@@ -186,7 +246,8 @@ class args:
         buildscript = False
         buildenv = False
         theme = None
+        pager = False
 
     # check invalid test name when querying result which will result in exception SystemExit
     with pytest.raises(SystemExit):
-        inspect_cmd(args)
+        inspect_cmd(args, configuration=configuration)
diff --git a/tests/cli/test_path.py b/tests/cli/test_path.py
index 559ceb3b4..ac3f1a25c 100644
--- a/tests/cli/test_path.py
+++ b/tests/cli/test_path.py
@@ -4,27 +4,38 @@
 import pytest
 from buildtest.cli.path import path_cmd
 from buildtest.cli.report import Report
+from buildtest.config import SiteConfiguration
+
+configuration = SiteConfiguration()
+configuration.detect_system()
+configuration.validate()
 
 
 def test_path():
-    report = Report()
+    report = Report(configuration=configuration)
     names = report.get_names()
     name = names[0]
 
-    path_cmd(name)
+    path_cmd(name, configuration=configuration)
 
-    path_cmd(name, outfile=True)
-    path_cmd(name, errfile=True)
-    path_cmd(name, stagedir=True)
-    path_cmd(name, testpath=True)
-    path_cmd(name, buildscript=True)
-    path_cmd(name, buildenv=True)
+    path_cmd(name, configuration=configuration, outfile=True)
+    path_cmd(name, configuration=configuration, errfile=True)
+    path_cmd(name, configuration=configuration, stagedir=True)
+    path_cmd(name, configuration=configuration, testpath=True)
+    path_cmd(name, configuration=configuration, buildscript=True)
+    path_cmd(name, configuration=configuration, buildenv=True)
 
     builders = report.builder_names()
     # specify name in format 'buildtest path <name>/<testid>
-    path_cmd(name=builders[0])
+    path_cmd(
+        name=builders[0],
+        configuration=configuration,
+    )
 
     random_test_name = "".join(random.choices(string.ascii_letters, k=10))
 
     with pytest.raises(SystemExit):
-        path_cmd(random_test_name)
+        path_cmd(
+            random_test_name,
+            configuration=configuration,
+        )
diff --git a/tests/cli/test_report.py b/tests/cli/test_report.py
index f811b1f80..b347d6c0a 100644
--- a/tests/cli/test_report.py
+++ b/tests/cli/test_report.py
@@ -5,17 +5,22 @@
 
 import pytest
 from buildtest.cli.report import Report, report_cmd, report_summary
+from buildtest.config import SiteConfiguration
 from buildtest.defaults import BUILD_REPORT, BUILDTEST_REPORTS, BUILDTEST_ROOT
 from buildtest.exceptions import BuildTestError
 from buildtest.utils.file import is_file
 from rich.color import Color
 
+configuration = SiteConfiguration()
+configuration.detect_system()
+configuration.validate()
+
 
 @pytest.mark.cli
 def test_report():
     # assert os.path.exists(BUILD_REPORT)
 
-    result = Report()
+    result = Report(configuration)
     print("Processing Report File:", result.reportfile())
 
     result.print_report()
@@ -26,25 +31,27 @@ def test_report():
     result.print_report(row_count=True)
 
     # run 'buildtest report --format name,state,returncode,buildspec'
-    result = Report(format_args="name,state,returncode,buildspec")
+    result = Report(
+        configuration=configuration, format_args="name,state,returncode,buildspec"
+    )
     result.print_report()
 
-    result = Report(pager=True)
+    result = Report(configuration, pager=True)
     result.print_report()
-    result = Report(color="red")
+    result = Report(configuration, color="red")
     result.print_report()
-    result = Report(color="red", pager=True)
+    result = Report(configuration, color="red", pager=True)
     result.print_report()
-    result = Report(color="BAD_COLOR")
+    result = Report(configuration, color="BAD_COLOR")
     result.print_report()
-    result = Report(color="BAD_COLOR", pager=True)
+    result = Report(configuration, color="BAD_COLOR", pager=True)
     result.print_report()
 
 
 @pytest.mark.cli
 def test_report_format():
     # buildtest report --helpformat
-    report = Report()
+    report = Report(configuration)
     report.print_format_fields()
 
     # buildtest report --formatfields
@@ -52,13 +59,13 @@ def test_report_format():
 
     # buildtest report --format XYZ is invalid format field
     with pytest.raises(BuildTestError):
-        Report(format_args="XYZ")
+        Report(configuration, format_args="XYZ")
 
 
 @pytest.mark.cli
 def test_report_filter():
     # run 'buildtest report --helpfilter'
-    report = Report()
+    report = Report(configuration)
     report.print_filter_fields()
 
     # run 'buildtest report --filterfields'
@@ -67,14 +74,17 @@ def test_report_filter():
     # run 'buildtest report --helpfilter'
     report.print_format_fields()
 
-    report = Report(filter_args={"state": "PASS"})
+    report = Report(configuration, filter_args={"state": "PASS"})
     report.print_report()
 
-    report = Report(filter_args={"state": "PASS"}, format_args="name,state")
+    report = Report(
+        configuration, filter_args={"state": "PASS"}, format_args="name,state"
+    )
     report.print_report()
 
     # run 'buildtest report --filter returncode=0,executor=generic.local.bash --format name,returncode,executor
     report = Report(
+        configuration=configuration,
         filter_args={"returncode": "0", "executor": "generic.local.bash"},
         format_args="name,returncode,executor",
     )
@@ -82,6 +92,7 @@ def test_report_filter():
 
     # run 'buildtest report --filter buildspec=tutorials/pass_returncode.yml --format name,returncode,buildspec
     Report(
+        configuration=configuration,
         filter_args={
             "buildspec": os.path.join(
                 BUILDTEST_ROOT, "tutorials", "test_status", "pass_returncode.yml"
@@ -91,13 +102,18 @@ def test_report_filter():
     )
 
     # run 'buildtest report --filter name=exit1_pass --format name,returncode,state
-    Report(filter_args={"name": "exit1_pass"}, format_args="name,returncode,state")
+    Report(
+        configuration=configuration,
+        filter_args={"name": "exit1_pass"},
+        format_args="name,returncode,state",
+    )
 
     # run 'buildtest report --filter returncode=-999 to ensure _filter_test_by_returncode returns True
-    Report(filter_args={"returncode": "-999"})
+    Report(configuration=configuration, filter_args={"returncode": "-999"})
 
     # Testing multiple filter fields. 'buildtest report --filter tags=tutorials,executor=generic.local.bash,state=PASS,returncode=0 --format name,returncode,state,executor,tags
     Report(
+        configuration=configuration,
         filter_args={
             "tags": "tutorials",
             "executor": "generic.local.bash",
@@ -111,25 +127,30 @@ def test_report_filter():
 @pytest.mark.cli
 def test_report_oldest_and_latest():
     # buildtest report --filter tags=tutorials --latest
-    Report(filter_args={"tags": "tutorials"}, latest=True)
+    Report(configuration=configuration, filter_args={"tags": "tutorials"}, latest=True)
 
     # buildtest report --filter tags=tutorials --oldest
-    Report(filter_args={"tags": "tutorials"}, oldest=True)
+    Report(configuration=configuration, filter_args={"tags": "tutorials"}, oldest=True)
 
     # buildtest report --filter tags=tutorials --oldest --latest
-    Report(filter_args={"tags": "tutorials"}, oldest=True, latest=True)
+    Report(
+        configuration=configuration,
+        filter_args={"tags": "tutorials"},
+        oldest=True,
+        latest=True,
+    )
 
 
 @pytest.mark.cli
 def test_report_failure():
     # buildtest report --filter tags=tutorials --failure
-    Report(filter_args={"tags": "tutorials"}, failure=True)
+    Report(configuration=configuration, filter_args={"tags": "tutorials"}, failure=True)
 
 
 @pytest.mark.cli
 def test_report_passed():
     # buildtest report --filter tags=tutorials --passed
-    Report(filter_args={"tags": "tutorials"}, passed=True)
+    Report(configuration=configuration, filter_args={"tags": "tutorials"}, passed=True)
 
 
 @pytest.mark.cli
@@ -138,13 +159,20 @@ def test_report_start_and_end():
     end_date = datetime.datetime.now()
 
     # buildtest report --filter tags=tutorials --start
-    Report(filter_args={"tags": "tutorials"}, start=start_date)
+    Report(
+        configuration=configuration, filter_args={"tags": "tutorials"}, start=start_date
+    )
 
     # buildtest report --filter tags=tutorials --end
-    Report(filter_args={"tags": "tutorials"}, end=end_date)
+    Report(configuration=configuration, filter_args={"tags": "tutorials"}, end=end_date)
 
     # buildtest report --filter tags=tutorials --start --end
-    Report(filter_args={"tags": "tutorials"}, start=start_date, end=end_date)
+    Report(
+        configuration=configuration,
+        filter_args={"tags": "tutorials"},
+        start=start_date,
+        end=end_date,
+    )
 
 
 @pytest.mark.cli
@@ -152,29 +180,38 @@ def test_invalid_filters():
     # run 'buildtest report --filter state=UNKNOWN --format name,state',
     # this raises error because UNKNOWN is not valid value for state field
     with pytest.raises(BuildTestError):
-        Report(filter_args={"state": "UNKNOWN"}, format_args="name,state")
+        Report(
+            configuration=configuration,
+            filter_args={"state": "UNKNOWN"},
+            format_args="name,state",
+        )
 
     tf = tempfile.NamedTemporaryFile(delete=True)
     tf.close()
     # test invalid buildspec file that doesn't exist in filesystem
     with pytest.raises(BuildTestError):
-        Report(filter_args={"buildspec": tf.name}, format_args="name,returncode,state")
+        Report(
+            configuration=configuration,
+            filter_args={"buildspec": tf.name},
+            format_args="name,returncode,state",
+        )
 
     # run 'buildtest report --filter buildspec=$HOME/.bashrc --format name,returncode,state
     # this will raise error even though file is valid it won't be found in cache
     with pytest.raises(BuildTestError):
         Report(
+            configuration=configuration,
             filter_args={"buildspec": "$HOME/.bashrc"},
             format_args="name,returncode,state",
         )
 
     # buildtest report --filter returncode=1.5 is invalid returncode (must be INT)
     with pytest.raises(BuildTestError):
-        Report(filter_args={"returncode": "1.5"})
+        Report(configuration=configuration, filter_args={"returncode": "1.5"})
 
     # buildtest report --filter XYZ=tutorials is invalid filter field
     with pytest.raises(BuildTestError):
-        Report(filter_args={"XYZ": "tutorials"})
+        Report(configuration=configuration, filter_args={"XYZ": "tutorials"})
 
 
 @pytest.mark.cli
@@ -183,56 +220,57 @@ def test_invalid_report_files():
 
     # reading a report file not in JSON format will result in exception BuildTestError
     with pytest.raises(BuildTestError):
-        Report(report_file=tf.name)
+        Report(configuration=configuration, report_file=tf.name)
 
     # closing the file will delete the file from file-system
     tf.close()
     # An invalid path for report file will raise an exception
     with pytest.raises(SystemExit):
-        Report(report_file=tf.name)
+        Report(configuration=configuration, report_file=tf.name)
 
     tempdir = tempfile.TemporaryDirectory()
     with pytest.raises(SystemExit):
-        Report(report_file=tempdir.name)
+        Report(configuration=configuration, report_file=tempdir.name)
 
 
 @pytest.mark.cli
 def test_report_summary():
-    report = Report()
-    report_summary(report)
+    report = Report(configuration=configuration)
+    report_summary(report, configuration=configuration)
 
     # buildtest rt summary --pager
-    report = Report(pager=True)
-    report_summary(report)
+    report = Report(configuration=configuration, pager=True)
+    report_summary(report, configuration=configuration)
 
     # buildtest rt summary --detailed
-    report = Report()
-    report_summary(report, detailed=True)
+    report = Report(configuration=configuration)
+    report_summary(report, configuration=configuration, detailed=True)
 
     # buildtest rt summary --detailed --pager
-    report = Report(pager=True)
-    report_summary(report, detailed=True)
+    report = Report(configuration=configuration, pager=True)
+    report_summary(report, configuration=configuration, detailed=True)
 
-    report = Report(color="light_pink1")
-    report_summary(report)
+    report = Report(configuration=configuration, color="light_pink1")
+    report_summary(report, configuration=configuration)
 
     report = Report(
+        configuration=configuration,
         pager=True,
         color="light_pink1",
     )
-    report_summary(report, detailed=True)
+    report_summary(report, configuration=configuration, detailed=True)
 
     # buildtest --color light_pink1 rt sm --detailed
-    report = Report(color="light_pink1")
-    report_summary(report, detailed=True)
+    report = Report(configuration=configuration, color="light_pink1")
+    report_summary(report, configuration=configuration, detailed=True)
 
     # buildtest rt sm --detailed --pager
-    report = Report(pager=True)
-    report_summary(report, detailed=True)
+    report = Report(configuration=configuration, pager=True)
+    report_summary(report, configuration=configuration, detailed=True)
 
     # buildtest --color BAD_COLOR rt sm --detailed
-    report = Report(color="BAD_COLOR")
-    report_summary(report, detailed=True)
+    report = Report(configuration=configuration, color="BAD_COLOR")
+    report_summary(report, configuration=configuration, detailed=True)
 
 
 @pytest.mark.cli
@@ -247,8 +285,9 @@ class args:
         report_subcommand = "list"
         terse = None
         color = None
+        pager = None
 
-    report_cmd(args)
+    report_cmd(args, configuration=configuration)
 
     backupfile = tempfile.NamedTemporaryFile()
     shutil.copy2(BUILDTEST_REPORTS, backupfile.name)
@@ -257,7 +296,7 @@ class args:
     os.remove(BUILDTEST_REPORTS)
 
     with pytest.raises(SystemExit):
-        report_cmd(args)
+        report_cmd(args, configuration=configuration)
 
     # move back the removed BUILDTEST_REPORTS file
     shutil.move(backupfile.name, BUILDTEST_REPORTS)
@@ -277,6 +316,7 @@ class args:
         terse = None
         no_header = None
         color = None
+        pager = None
 
     backupfile_report = tempfile.NamedTemporaryFile()
     shutil.copy2(BUILD_REPORT, backupfile_report.name)
@@ -284,11 +324,11 @@ class args:
     backupfile_list_report = tempfile.NamedTemporaryFile()
     shutil.copy2(BUILDTEST_REPORTS, backupfile_list_report.name)
 
-    report_cmd(args)
+    report_cmd(args, configuration=configuration)
 
     # buildtest report clear will raise an error since file doesn't exist
     with pytest.raises(SystemExit):
-        report_cmd(args)
+        report_cmd(args, configuration=configuration)
 
     assert not is_file(BUILD_REPORT)
     # move back the backe-up files since report_cmd() function removes the files BUILD_REPORT and BUILDTEST_REPORTS
@@ -302,7 +342,7 @@ class args:
 
 @pytest.mark.cli
 def test_report_limited_rows():
-    report = Report()
+    report = Report(configuration=configuration)
     report.print_report(count=5)
     report.print_report(terse=True, count=5)
 
@@ -321,5 +361,6 @@ class args:
         report_subcommand = "path"
         count = None
         color = None
+        pager = None
 
-    report_cmd(args)
+    report_cmd(args, configuration=configuration)
diff --git a/tests/examples/cori/burstbuffer.yml b/tests/examples/cori/burstbuffer.yml
deleted file mode 100644
index d813d3d35..000000000
--- a/tests/examples/cori/burstbuffer.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-buildspecs:
-  stage_in_out_burst_buffer:
-    type: script
-    executor: cori.slurm.knl_debug
-    tags: [datawarp, jobs]
-    description: Stage in data to Burst Buffer
-    sbatch: ["-t 10", "-N 1", "-n 4"]
-    DW:
-      - "jobdw capacity=1GB access_mode=striped type=scratch"
-      - "stage_in source=$SCRATCH/stage_in.txt destination=$DW_JOB_STRIPED/stage_in.txt type=file"
-    run: |
-      cd $SCRATCH
-      dd if=/dev/urandom of=stage_in.txt bs=1G count=1 iflag=fullblock
-      ls -lh ${DW_JOB_STRIPED}/stage_in.txt
-      rm  $SCRATCH/stage_in.txt
diff --git a/tests/examples/cori/exists.yml b/tests/examples/cori/exists.yml
deleted file mode 100644
index f8c8f3ef9..000000000
--- a/tests/examples/cori/exists.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-buildspecs:
-  status_exists_slurm:
-    type: script
-    executor: cori.slurm.knl_debug
-    description: status check based for file and directory
-    sbatch: ['-n 1', '-t 5', '-C knl']
-    run: |
-      mkdir -p a/b/c
-      touch foo
-    status:
-      exists:
-        - a/b/c
-        - foo
-      is_dir:
-        - a/b/c
-      is_file:
-        - foo
diff --git a/tests/examples/cori/hostname.yml b/tests/examples/cori/hostname.yml
deleted file mode 100644
index 13adaf897..000000000
--- a/tests/examples/cori/hostname.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-buildspecs:
-  hostname_knl:
-    type: script
-    executor: cori.slurm.knl_debug
-    description: "Run hostname via KNL debug queue"
-    sbatch: ["-t 5", "-n 1"]
-    run: hostname
diff --git a/tests/examples/cori/hold_job.yml b/tests/examples/perlmutter/hold_job.yml
similarity index 63%
rename from tests/examples/cori/hold_job.yml
rename to tests/examples/perlmutter/hold_job.yml
index 1c4b8e273..3f5756eb5 100644
--- a/tests/examples/cori/hold_job.yml
+++ b/tests/examples/perlmutter/hold_job.yml
@@ -1,7 +1,7 @@
 buildspecs:
   hold_job:
     type: script
-    executor: cori.slurm.knl_debug
+    executor: perlmutter.slurm.debug
     description: "Hold job will be cancelled by scheduler after maxpendtime reached"
-    sbatch: ["-t 5", "-N 1", "-H"]
+    sbatch: ["-t 5", "-N 1", "-H", "-C cpu"]
     run: hostname
diff --git a/tests/examples/perlmutter/hostname.yml b/tests/examples/perlmutter/hostname.yml
new file mode 100644
index 000000000..ce3a5b4cf
--- /dev/null
+++ b/tests/examples/perlmutter/hostname.yml
@@ -0,0 +1,7 @@
+buildspecs:
+  hostname:
+    type: script
+    executor: perlmutter.slurm.debug
+    description: "Run hostname via slurm debug queue"
+    sbatch: ["-t 5", "-n 1", "-C cpu"]
+    run: hostname
diff --git a/tests/schema_tests/test_settings.py b/tests/schema_tests/test_settings.py
index b374b9efc..0f7eec06b 100644
--- a/tests/schema_tests/test_settings.py
+++ b/tests/schema_tests/test_settings.py
@@ -4,6 +4,7 @@
 from buildtest.defaults import SCHEMA_ROOT
 from buildtest.schemas.defaults import custom_validator
 from buildtest.schemas.utils import load_recipe, load_schema
+from jsonschema.exceptions import ValidationError
 
 schema_file = "settings.schema.json"
 settings_schema = os.path.join(SCHEMA_ROOT, schema_file)
@@ -19,7 +20,7 @@ def test_settings_examples():
     assert valid
 
     valid_recipes = os.listdir(valid)
-    assert valid_recipes
+
     # check all valid recipes
     for example in valid_recipes:
         filepath = os.path.join(valid, example)
@@ -29,3 +30,14 @@ def test_settings_examples():
 
         print(f"Expecting Recipe File: {filepath} to be valid")
         custom_validator(recipe=example_recipe, schema=recipe)
+
+    invalid = os.path.join(settings_schema_examples, "invalid")
+    for example in os.listdir(invalid):
+        filepath = os.path.join(invalid, example)
+        print(f"Loading Recipe File: {filepath}")
+        example_recipe = load_recipe(filepath)
+        assert example_recipe
+
+        print(f"Expecting Recipe File: {filepath} to be invalid")
+        with pytest.raises(ValidationError):
+            custom_validator(recipe=example_recipe, schema=recipe)
diff --git a/tests/settings/ascent.yml b/tests/settings/ascent.yml
index db5a2ffc2..882d00c3b 100644
--- a/tests/settings/ascent.yml
+++ b/tests/settings/ascent.yml
@@ -3,6 +3,7 @@ system:
     hostnames:
     - login1.ascent.olcf.ornl.gov
     moduletool: lmod
+    pager: False
     executors:
       defaults:
         pollinterval: 30
diff --git a/tests/settings/jlse.yml b/tests/settings/jlse.yml
index f65a126b7..762eaec8d 100644
--- a/tests/settings/jlse.yml
+++ b/tests/settings/jlse.yml
@@ -3,6 +3,7 @@ system:
     hostnames:
     - jlselogin*
     moduletool: environment-modules
+    pager: False
     executors:
       defaults:
         pollinterval: 30
diff --git a/tests/settings/nersc.yml b/tests/settings/nersc.yml
index 39b6504d6..58fd23c2b 100644
--- a/tests/settings/nersc.yml
+++ b/tests/settings/nersc.yml
@@ -1,65 +1,8 @@
 system:
-  cori:
-    hostnames: ["cori*"]
-    moduletool: environment-modules
-    compilers:
-      enable_prgenv: true
-      prgenv_modules:
-        gcc: PrgEnv-gnu
-        cray: PrgEnv-cray
-        intel: PrgEnv-intel
-      find:
-        gcc: '^gcc'
-        cray: '^(craype/)'
-        intel: '^intel'
-        cuda: '^(cuda/)'
-        upcxx: '^(upcxx/)'
-      compiler:
-        gcc:
-          builtin_gcc:
-            cc: /usr/bin/gcc
-            cxx: /usr/bin/g++
-            fc: /usr/bin/gfortran
-    executors:
-      defaults:
-        pollinterval: 30
-        maxpendtime: 300
-      local:
-        bash:
-          description: submit jobs on local machine using bash shell
-          shell: bash
-        sh:
-          description: submit jobs on local machine using sh shell
-          shell: sh
-        csh:
-          description: submit jobs on local machine using csh shell
-          shell: csh
-        python:
-          description: submit jobs on local machine using python shell
-          shell: python
-
-      slurm:
-        haswell_debug:
-          qos: debug
-          cluster: cori
-          options:
-          - -C haswell
-          description: debug queue on Haswell partition
-
-        knl_debug:
-          qos: debug
-          cluster: cori
-          options:
-            - -C knl
-          description: debug queue on KNL partition
-        bigmem:
-          qos: bigmem
-          cluster: escori
-          description: large memory jobs
-
   perlmutter:
     hostnames: ["login*"]
     moduletool: lmod
+    pager: False
     compilers:
       enable_prgenv: true
       prgenv_modules:
@@ -93,3 +36,7 @@ system:
         zsh:
           description: submit jobs on local machine using zsh shell
           shell: zsh
+      slurm:
+        debug:
+          description: submit jobs on perlmutter using slurm debug queue
+          qos: debug
diff --git a/tests/settings/pbs.yml b/tests/settings/pbs.yml
index dd245194e..744a19bdd 100644
--- a/tests/settings/pbs.yml
+++ b/tests/settings/pbs.yml
@@ -2,6 +2,7 @@ system:
   generic:
     hostnames: ['.*']
     moduletool: N/A
+    pager: False
     executors:
       defaults:
         pollinterval: 5
diff --git a/tests/test_nersc.py b/tests/test_nersc.py
index de3b3801e..2be9b4c5e 100644
--- a/tests/test_nersc.py
+++ b/tests/test_nersc.py
@@ -1,5 +1,6 @@
 import os
 import socket
+import tempfile
 
 import pytest
 from buildtest.cli.build import BuildTest
@@ -9,164 +10,78 @@
 from buildtest.defaults import BUILDTEST_ROOT
 from buildtest.system import BuildTestSystem
 
-hostname = socket.getfqdn()
-here = os.path.dirname(os.path.abspath(__file__))
 
-settings_file = os.path.join(here, "settings", "nersc.yml")
+class TestNersc:
+    hostname = socket.getfqdn()
+    here = os.path.dirname(os.path.abspath(__file__))
 
+    if not hostname.startswith("login"):
+        pytest.skip(
+            "This test runs on Perlmutter Login nodes ('login*')",
+            allow_module_level=True,
+        )
 
-def test_cori_burstbuffer():
-    # This test must run on Cori Login nodes which are cori[01-20].nersc.gov.
-
-    if not hostname.startswith("cori"):
-        pytest.skip("This test runs on Cori Login nodes ('cori*')")
+    settings_file = os.path.join(here, "settings", "nersc.yml")
 
     system = BuildTestSystem()
 
     bc = SiteConfiguration(settings_file)
     bc.detect_system()
-    bc.validate(moduletool="environment-modules")
+    bc.validate(moduletool="lmod")
     BuildspecCache(rebuild=True, configuration=bc)
-    cmd = BuildTest(
-        configuration=bc,
-        buildspecs=[
-            os.path.join(
-                os.getenv("BUILDTEST_ROOT"),
-                "tests",
-                "examples",
-                "cori",
-                "burstbuffer.yml",
-            )
-        ],
-        buildtest_system=system,
-        stage="build",
-    )
-    cmd.build()
-
-
-def test_cori_slurm_hostname():
-    if not hostname.startswith("cori"):
-        pytest.skip("This test runs on Cori Login nodes ('cori*')")
-
-    system = BuildTestSystem()
-
-    bc = SiteConfiguration(settings_file)
-    bc.detect_system()
-    bc.validate(moduletool="environment-modules")
-
-    cmd = BuildTest(
-        configuration=bc,
-        buildspecs=[
-            os.path.join(BUILDTEST_ROOT, "tests", "examples", "cori", "hostname.yml")
-        ],
-        buildtest_system=system,
-        poll_interval=5,
-        maxpendtime=120,
-        numprocs=[1, 4],
-    )
-    cmd.build()
-
-
-def test_cori_slurm_max_pend():
-    if not hostname.startswith("cori"):
-        pytest.skip("This test runs on Cori Login nodes ('cori*')")
-
-    bc = SiteConfiguration(settings_file)
-    bc.detect_system()
-    bc.validate(moduletool="environment-modules")
-
-    system = BuildTestSystem()
 
-    cmd = BuildTest(
-        configuration=bc,
-        buildspecs=[
-            os.path.join(
-                os.getenv("BUILDTEST_ROOT"), "tests", "examples", "cori", "hold_job.yml"
-            )
-        ],
-        buildtest_system=system,
-        poll_interval=5,
-        maxpendtime=10,
-    )
-    with pytest.raises(SystemExit):
+    def test_slurm_hostname(self):
+        cmd = BuildTest(
+            configuration=self.bc,
+            buildspecs=[
+                os.path.join(
+                    BUILDTEST_ROOT, "tests", "examples", "perlmutter", "hostname.yml"
+                )
+            ],
+            buildtest_system=self.system,
+            poll_interval=5,
+            maxpendtime=120,
+            numprocs=[1, 4],
+        )
         cmd.build()
 
-
-def test_cori_slurm_file_exists():
-    if not hostname.startswith("cori"):
-        pytest.skip("This test runs on Cori Login nodes ('cori*')")
-
-    bc = SiteConfiguration(settings_file)
-    bc.detect_system()
-    bc.validate(moduletool="environment-modules")
-
-    system = BuildTestSystem()
-
-    cmd = BuildTest(
-        configuration=bc,
-        buildspecs=[
-            os.path.join(
-                os.getenv("BUILDTEST_ROOT"), "tests", "examples", "cori", "exists.yml"
-            )
-        ],
-        buildtest_system=system,
-        poll_interval=5,
-        maxpendtime=120,
-    )
-    cmd.build()
-
-
-def test_compiler_find_cori():
-    if not hostname.startswith("cori"):
-        pytest.skip("This test runs on Cori Login nodes ('cori*')")
-
-    bc = SiteConfiguration(settings_file)
-    bc.detect_system()
-    bc.validate(moduletool="environment-modules")
-
-    # testing buildtest config compilers find
-    compilers = BuildtestCompilers(configuration=bc)
-    compilers.find_compilers()
-
-    # test entry point for 'buildtest config compilers find --detailed'
-    compiler_find(configuration=bc, detailed=True)
-
-
-def test_compiler_test_cori():
-    if not hostname.startswith("cori"):
-        pytest.skip("This test runs on Cori Login nodes ('cori*')")
-
-    bc = SiteConfiguration(settings_file)
-    bc.detect_system()
-    bc.validate(moduletool="environment-modules")
-
-    # testing buildtest config compilers test
-    compiler_test(configuration=bc)
-
-
-def test_compiler_find_perlmutter():
-    if not hostname.startswith("login"):
-        pytest.skip("This test runs on Perlmutter Login nodes ('login*')")
-
-    bc = SiteConfiguration(settings_file)
-    bc.detect_system()
-    bc.validate(moduletool="lmod")
-
-    # testing buildtest config compilers find
-    compilers = BuildtestCompilers(configuration=bc)
-    compilers.find_compilers()
-
-    # test entry point for 'buildtest config compilers find --detailed'
-    compiler_find(configuration=bc, detailed=True)
-
-
-def test_compiler_test_perlmutter():
-    if not hostname.startswith("login"):
-        pytest.skip("This test runs on Perlmutter Login nodes ('login*')")
-
-    bc = SiteConfiguration(settings_file)
-    bc.detect_system()
-    bc.validate(moduletool="lmod")
-
-    # testing buildtest config compilers test
-    compiler_test(configuration=bc)
+    def test_slurm_max_pend(self):
+        cmd = BuildTest(
+            configuration=self.bc,
+            buildspecs=[
+                os.path.join(
+                    os.getenv("BUILDTEST_ROOT"),
+                    "tests",
+                    "examples",
+                    "perlmutter",
+                    "hold_job.yml",
+                )
+            ],
+            buildtest_system=self.system,
+            poll_interval=5,
+            maxpendtime=10,
+        )
+        with pytest.raises(SystemExit):
+            cmd.build()
+
+    def test_compiler_find(self):
+        # testing buildtest config compilers find
+        compilers = BuildtestCompilers(configuration=self.bc)
+        compilers.find_compilers()
+
+        # test entry point for 'buildtest config compilers find --detailed'
+        compiler_find(configuration=self.bc, detailed=True)
+
+    def test_compiler_test(self):
+        # testing buildtest config compilers test
+        compiler_test(configuration=self.bc)
+
+    def test_compiler_find_alternative_filepath(self):
+        # testing buildtest config compilers find
+        compilers = BuildtestCompilers(configuration=self.bc)
+        compilers.find_compilers()
+
+        # test entry point for 'buildtest config compilers find --file'
+        temp_path = tempfile.NamedTemporaryFile(dir=os.path.expanduser("~"))
+        compiler_find(configuration=self.bc, filepath=temp_path.name)
+        temp_path.close()
diff --git a/tests/test_stats.py b/tests/test_stats.py
index 20202863b..94684eb49 100644
--- a/tests/test_stats.py
+++ b/tests/test_stats.py
@@ -17,6 +17,6 @@ def test_stats():
     cmd = BuildTest(configuration=configuration, tags=["pass"], buildtest_system=system)
     cmd.build()
 
-    report = Report()
+    report = Report(configuration=configuration)
     name = report.get_random_tests()
-    stats_cmd(name[0])
+    stats_cmd(name[0], configuration=configuration)
diff --git a/tutorials/test_status/mode.yml b/tutorials/test_status/mode.yml
new file mode 100644
index 000000000..60e42cac8
--- /dev/null
+++ b/tutorials/test_status/mode.yml
@@ -0,0 +1,28 @@
+buildspecs:
+  status_logical_and:
+    type: script
+    executor: 'generic.local.bash'
+    description: 'Using logical AND to check status'
+    run: |
+      echo "This is a test"
+      exit 1
+    status:
+      mode: all
+      returncode: 1
+      regex:
+        stream: stdout
+        exp: 'This is a test'
+
+  status_logical_or:
+    type: script
+    executor: 'generic.local.bash'
+    description: 'Using logical OR to check status'
+    run: |
+      echo "This is a test"
+      exit 1
+    status:
+      mode: any
+      returncode: 0
+      regex:
+        stream: stdout
+        exp: 'This is a test'
\ No newline at end of file