{"id":13413863,"url":"https://github.com/commander-cli/commander","last_synced_at":"2026-01-12T14:48:29.700Z","repository":{"id":37855356,"uuid":"172099405","full_name":"commander-cli/commander","owner":"commander-cli","description":"Test your command line interfaces on windows, linux and osx and nodes viá ssh and docker","archived":false,"fork":false,"pushed_at":"2024-04-02T16:21:30.000Z","size":10290,"stargazers_count":228,"open_issues_count":33,"forks_count":19,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-10-24T14:08:38.330Z","etag":null,"topics":["cli","cmd","command-line","golang","linux","osx","sh","shell","terminal","testing","windows"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/commander-cli.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2019-02-22T16:35:16.000Z","updated_at":"2025-01-20T11:04:12.000Z","dependencies_parsed_at":"2024-04-02T17:30:35.661Z","dependency_job_id":"4f3a5c54-a8a0-4c4e-ae17-91fb99b74e7c","html_url":"https://github.com/commander-cli/commander","commit_stats":null,"previous_names":["simonbaeumer/commander"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/commander-cli/commander","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/commander-cli%2Fcommander","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/commander-cli%2Fcommander/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/commander-cli%2Fcommander/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/commander-cli%2Fcommander/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/commander-cli","download_url":"https://codeload.github.com/commander-cli/commander/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/commander-cli%2Fcommander/sbom","scorecard":{"id":300782,"data":{"date":"2025-08-11","repo":{"name":"github.com/commander-cli/commander","commit":"11660f4cd4e309c3225a5df7925b4a2daceeafee"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.3,"checks":[{"name":"Code-Review","score":6,"reason":"Found 18/27 approved changesets -- score normalized to 6","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v2.5.0 not signed: https://api.github.com/repos/commander-cli/commander/releases/97222471","Warn: release artifact v2.4.0 not signed: https://api.github.com/repos/commander-cli/commander/releases/33097755","Warn: release artifact v2.3.0 not signed: https://api.github.com/repos/commander-cli/commander/releases/29545936","Warn: release artifact v2.2.0 not signed: https://api.github.com/repos/commander-cli/commander/releases/28710188","Warn: release artifact v2.1.0 not signed: https://api.github.com/repos/commander-cli/commander/releases/27276146","Warn: release artifact v2.5.0 does not have provenance: https://api.github.com/repos/commander-cli/commander/releases/97222471","Warn: release artifact v2.4.0 does not have provenance: https://api.github.com/repos/commander-cli/commander/releases/33097755","Warn: release artifact v2.3.0 does not have provenance: https://api.github.com/repos/commander-cli/commander/releases/29545936","Warn: release artifact v2.2.0 does not have provenance: https://api.github.com/repos/commander-cli/commander/releases/28710188","Warn: release artifact v2.1.0 does not have provenance: https://api.github.com/repos/commander-cli/commander/releases/27276146"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: containerImage not pinned by hash: integration/containers/ssh/Dockerfile:1: pin your Docker image by updating ubuntu:18.04 to ubuntu:18.04@sha256:152dc042452c496007f07ca9127571cb9c29697f42acbfad72324b2bb2e43c98","Warn: containerImage not pinned by hash: integration/containers/test/Dockerfile:1: pin your Docker image by updating golang:1.21-bullseye to golang:1.21-bullseye@sha256:40a67e6626bead90d5c7957bd0354cfeb8400e61acc3adc256e03252630014a6","Info:   0 out of   2 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Vulnerabilities","score":0,"reason":"10 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2024-2512 / GHSA-xw73-rw38-6vjc","Warn: Project is vulnerable to: GO-2024-3005 / GHSA-v23v-6jw2-98fq","Warn: Project is vulnerable to: GO-2025-3829 / GHSA-4vq8-7jfc-9cvp","Warn: Project is vulnerable to: GO-2023-2402 / GHSA-45x7-px36-x8w8","Warn: Project is vulnerable to: GO-2024-3321 / GHSA-v778-237x-gjrc","Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77","Warn: Project is vulnerable to: GO-2024-2687 / GHSA-4v7x-pqxf-cx7m","Warn: Project is vulnerable to: GO-2024-3333","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 29 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T20:34:38.214Z","repository_id":37855356,"created_at":"2025-08-17T20:34:38.214Z","updated_at":"2025-08-17T20:34:38.214Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28340410,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T12:22:26.515Z","status":"ssl_error","status_checked_at":"2026-01-12T12:22:10.856Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","cmd","command-line","golang","linux","osx","sh","shell","terminal","testing","windows"],"created_at":"2024-07-30T20:01:51.462Z","updated_at":"2026-01-12T14:48:29.673Z","avatar_url":"https://github.com/commander-cli.png","language":"Go","readme":"[![Build Status](https://travis-ci.org/commander-cli/commander.svg?branch=master)](https://travis-ci.org/commander-cli/commander)\n[![GoDoc](https://godoc.org/github.com/commander-cli/commander?status.svg)](https://godoc.org/github.com/commander-cli/commander)\n[![Go Report Card](https://goreportcard.com/badge/github.com/commander-cli/commander)](https://goreportcard.com/report/github.com/commander-cli/commander)\n[![Maintainability](https://api.codeclimate.com/v1/badges/cc848165784e0f809a51/maintainability)](https://codeclimate.com/github/commander-cli/commander/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/cc848165784e0f809a51/test_coverage)](https://codeclimate.com/github/commander-cli/commander/test_coverage)\n[![Github All Releases](https://img.shields.io/github/downloads/commander-cli/commander/total.svg)](https://github.com/commander-cli/commander/releases)\n\n# Commander\n\nDefine language independent tests for your command line scripts and programs in simple `yaml` files.\n\n - It runs on `windows`, `osx` and `linux` \n - It can validate local machines, ssh hosts and docker containers\n - It is a self-contained binary - no need to install a heavy lib or language\n - It is easy and fast to write\n\nFor more information take a look at the [quick start](#quick-start), the [examples](examples) or the [integration tests](integration).\n\n## Table of contents\n\n* [Installation](#installation)\n  + [Any system with Go installed](#any-system-with-go-installed)\n  + [Linux \u0026 osx](#linux---osx)\n  + [Windows](#windows)\n* [Quick start](#quick-start)\n  + [Complete YAML file](#complete-yaml-file)\n  + [Executing](#executing)\n  + [Adding tests](#adding-tests)\n* [Documentation](#documentation)\n  + [Usage](#usage)\n  + [Tests](#tests)\n    - [command](#command)\n    - [config](#user-content-config-test)\n    - [exit-code](#exit-code)\n    - [stdout](#stdout)\n      * [contains](#contains)\n      * [exactly](#exactly)\n      * [json](#json)\n      * [lines](#lines)\n      * [line-count](#line-count)\n      * [not-contains](#not-contains)\n      * [xml](#xml)\n      * [file](#file)\n    - [stderr](#stderr)\n    - [skip](#skip)\n  + [Config](#user-content-config-config)\n    - [dir](#dir)\n    - [env](#env)\n    - [inherit-env](#inherit-env)\n    - [interval](#interval)\n    - [retries](#retries)\n    - [timeout](#timeout)\n    - [nodes](#nodes)\n  + [Nodes](#nodes)\n    - [local](#local)\n    - [ssh](#ssh)\n    - [docker](#docker)\n  + [Development](#development)\n* [Misc](#misc)\n\n## Installation\n\n### Any system with Go installed\n\nProbably the easiest way to install `commander` is by using `go get` to download and install it in one simple command:\n\n```bash\ngo install github.com/commander-cli/commander/v2/cmd/commander@latest\n```\n\nThis works on any OS, as long as go is installed. If go is not installed on your system, follow one of the methods below.\n\n### Linux \u0026 osx\n\nVisit the [release](https://github.com/commander-cli/commander/releases) page to get the binary for you system. \n\n```bash\ncurl -L https://github.com/commander-cli/commander/releases/download/v2.5.0/commander-linux-amd64 -o commander\nchmod +x commander\n```\n\n### Windows\n\n - Download the current [release](https://github.com/commander-cli/commander/releases/latest)\n - Add the path to your [path](https://docs.alfresco.com/4.2/tasks/fot-addpath.html) environment variable\n - Test it: `commander --version`\n\n\n## Quick start\n\nA `commander` test suite consists of a `config` and `tests` root element. To start quickly you can use \nthe following examples.\n\n```bash\n# You can even let commander add tests for you!\n$ ./commander add --stdout --file=/tmp/commander.yaml echo hello\ntests:\n  echo hello:\n    exit-code: 0\n    stdout: hello\n\nwritten to /tmp/commander.yaml\n\n# ... and execute!\n$ ./commander test /tmp/commander.yaml\nStarting test file /tmp/commander.yaml...\n\n✓ echo hello\n\nDuration: 0.002s\nCount: 1, Failed: 0\n```\n\n### Complete YAML file\n\nHere you can see an example with all features for a quick reference\n\n```yaml\nnodes:\n  ssh-host1:\n    type: ssh\n    addr: 192.168.0.1:22\n    user: root\n    pass: pass\n  ssh-host2:\n    type: ssh\n    addr: 192.168.0.1:22\n    user: root\n    identity-file: /home/user/id_rsa.pub\n  docker-host1:\n    type: docker\n    image: alpine:2.4\n  docker-host2:\n    type: docker\n    instance: alpine_instance_1\n\nconfig: # Config for all executed tests\n    dir: /tmp #Set working directory\n    env: # Environment variables\n        KEY: global\n    timeout: 50s # Define a timeout for a command under test\n    retries: 2 # Define retries for each test\n    nodes:\n    - ssh-host1 # define default hosts\n    \ntests:\n    echo hello: # Define command as title\n        stdout: hello # Default is to check if it contains the given characters\n        exit-code: 0 # Assert exit-code\n        \n    it should skip:\n        command: echo \"I should be skipped\"\n        stdout: I should be skipped\n        skip: true\n        \n    it should fail:\n        command: invalid\n        stderr:\n            contains: \n                - invalid # Assert only contain work\n            not-contains:\n                - not in there # Validate that a string does not occur in stdout\n            exactly: \"/bin/sh: 1: invalid: not found\"\n            line-count: 1 # Assert amount of lines\n            lines: # Assert specific lines\n                1: \"/bin/sh: 1: invalid: not found\"\n            json:\n                object.attr: hello # Make assertions on json objects\n            xml:\n                \"//book//auhtor\": Steven King # Make assertions on xml documents\n            file: correct-output.txt\n        exit-code: 127\n        skip: false\n\n    it has configs:\n        command: echo hello\n        stdout:\n            contains: \n              - hello #See test \"it should fail\"\n            exactly: hello\n            line-count: 1\n        config:\n            inherit-env: true # You can inherit the parent shells env variables\n            dir: /home/user # Overwrite working dir\n            env:\n                KEY: local # Overwrite env variable\n                ANOTHER: yeah # Add another env variable\n            timeout: 1s # Overwrite timeout\n            retries: 5\n            nodes: # overwrite default nodes\n              - docker-host1\n              - docker-host2\n        exit-code: 0\n```\n\n### Executing\n\n```bash\n# Execute file commander.yaml in current directory\n$ ./commander test \n\n# Execute a specific suite\n$ ./commander test /tmp/test.yaml\n\n# Execute a single test\n$ ./commander test /tmp/test.yaml --filter \"my test\"\n\n# Execute suite from stdin\n$ cat /tmp/test.yaml | ./commander test -\n\n# Execute suite from url\n$ ./commander test https://your-url/commander_test.yaml\n\n# Execute suites within a test directory\n$ ./commander test --dir /tmp\n\n# Execute suites in a different working directory \n$ ./commander test --workdir /examples minimal_test.yaml\n```\n\n### Adding tests\n\nYou can use the `add` argument if you want to `commander` to create your tests.\n\n```bash\n# Add a test to the default commander.yaml\n$ ./commander add echo hello\nwritten to /tmp/commander.yaml\n\n# Write to a given file\n$ ./commander add --file=test.yaml echo hello\nwritten to test.yaml\n\n# Write to stdout and file\n$ ./commander add --stdout echo hello\ntests:\n  echo hello:\n    exit-code: 0\n    stdout: hello\n\nwritten to /tmp/commander.yaml\n\n# Only to stdout\n$ ./commander add --stdout --no-file echo hello\ntests:\n  echo hello:\n    exit-code: 0\n    stdout: hello\n```\n\n## Documentation\n\n### Usage\n\n```\nNAME:\n   Commander - CLI app testing\n\nUSAGE:\n   commander [global options] command [command options] [arguments...]\n\nCOMMANDS:\n     test     Execute the test suite\n     add      Automatically add a test to your test suite\n     help, h  Shows a list of commands or help for one command\n\nGLOBAL OPTIONS:\n   --help, -h     show help\n   --version, -v  print the version\n```\n\n\n### Tests\n\nTests are defined in the `tests` root element. Every test consists of a [command](#command) and an expected result, \ni.e. an [exit-code](#exit-code). \n\n\n```yaml\ntests: # root element\n  echo test: # test case - can either be the command or a given title\n    stdout: test\n    exit-code: 0\n```\n\nA test is a `map` which configures the test. \nThe `key` (`echo test` in the example above) of the test can either be the `command` itself or the `title` of the test which will be displayed in the test execution.\n\nIf the same `command` is tested multiple times it is useful to set the `title` of the test manually and use the `command` property. \nFurther the `title` can be useful to describe tests better. See [the commander test suite](commander_unix.yaml) as an example.\n\n - name: `title or command under test`\n - type: `map`\n - default: `{}`\n \nExamples:\n\n```yaml\ntests:\n  echo test: # command and title will be the same\n    stdout: test\n    exit-code: 0\n    \n  my title: # custom title\n    command: exit 1 # set command manually\n    exit-code: 1\n```\n\n#### command\n\n`command` is a `string` containing the `command` to be tested. Further the `command` property is automatically parsed from \nthe `key` if no `command` property was given.\n\n - name: `command`\n - type: `string`\n - default: `can't be empty`\n - notes: Will be parsed from the `key` if no `command` property was provided and used as the title too\n \n \n```yaml\necho test: # use command as key and title\n  exit-code: 0\n  \nit should print hello world: # use a more descriptive title...\n  command: echo hello world  # ... and set the command in the property manually\n  stdout: hello world\n  exit-code: 0\n```\n\n#### \u003ca name=\"config-test\"\u003e\u003c/a\u003econfig\n\n`config` sets configuration for the test. `config` can overwrite global configurations. \n\n - name: `config`\n - type: `map`\n - default: `{}`\n - notes:\n   - for more information look at [config](#user-content-config-config)\n\n```yaml\necho test:\n  config:\n    timeout: 5s\n```\n\n#### exit-code\n\n`exit-code` is an `int` type and compares the given code to the `exit-code` of the given command.\n\n - name: `exit-code`\n - type: `int`\n - default: `0`\n \n```yaml\nexit 1: # will pass\n  exit-code: 1\nexit 0: # will fail\n  exit-code: 1\n```\n\n#### stdout\n\n`stdout` and `stderr` allow to make assertions on the output of the command. \nThe type can either be a `string` or a `map` of different assertions.\n\nIf only a `string` is provided it will check if the given string is [contained](#contains) in the output.\n\n - name: `stdout`\n - type: `string` or `map`\n - default: ` `\n - notes: [stderr](#stderr) works the same way\n \n```yaml\necho test:\n  stdout: test # make a contains assertion\n  \necho hello world:\n  stdout:\n    line-count: 1 # assert the amount of lines and use stdout as a map\n```\n\n##### contains\n\n`contains` is an `array` or `string`. It checks if a `string` is contained in the output. \nIt is the default if a `string` is directly assigned to `stdout` or `stderr`.\n\n - name: `contains`\n - type: `string` or `array`\n - default: `[]`\n - notes: default assertion if directly assigned to `stdout` or `stderr`\n\n```yaml\necho hello world:\n  stdout: hello # Default is a contains assertion\n\necho more output:\n  stdout:\n    contains:\n      - more\n      - output\n```\n\n##### exactly\n\n`exactly` is a `string` type which matches the exact output.\n\n - name: `exactly`\n - type: `string`\n - default: ` `\n \n```yaml\necho test:\n  stdout:\n    exactly: test\n```\n\n##### json\n\n`json` is a `map` type and allows to parse `json` documents with a given `GJSON syntax` to query for specific data. \nThe `key` represents the query, the `value` the expected value.\n\n - name: `json`\n - type: `map`\n - default: `{}`\n - notes: Syntax taken from [GJSON](https://github.com/tidwall/gjson#path-syntax)\n \n```yaml\ncat some.json: # print json file to stdout\n  name.last: Anderson # assert on name.last, see document below\n``` \n\n`some.json` file:\n \n```json\n{\n  \"name\": {\"first\": \"Tom\", \"last\": \"Anderson\"},\n  \"age\":37,\n  \"children\": [\"Sara\",\"Alex\",\"Jack\"],\n  \"fav.movie\": \"Deer Hunter\",\n  \"friends\": [\n    {\"first\": \"Dale\", \"last\": \"Murphy\", \"age\": 44, \"nets\": [\"ig\", \"fb\", \"tw\"]},\n    {\"first\": \"Roger\", \"last\": \"Craig\", \"age\": 68, \"nets\": [\"fb\", \"tw\"]},\n    {\"first\": \"Jane\", \"last\": \"Murphy\", \"age\": 47, \"nets\": [\"ig\", \"tw\"]}\n  ]\n}\n```\n\nMore examples queries:\n\n```\n\"name.last\"          \u003e\u003e \"Anderson\"\n\"age\"                \u003e\u003e 37\n\"children\"           \u003e\u003e [\"Sara\",\"Alex\",\"Jack\"]\n\"children.#\"         \u003e\u003e 3\n\"children.1\"         \u003e\u003e \"Alex\"\n\"child*.2\"           \u003e\u003e \"Jack\"\n\"c?ildren.0\"         \u003e\u003e \"Sara\"\n\"fav\\.movie\"         \u003e\u003e \"Deer Hunter\"\n\"friends.#.first\"    \u003e\u003e [\"Dale\",\"Roger\",\"Jane\"]\n\"friends.1.last\"     \u003e\u003e \"Craig\"\n```\n\n##### lines\n\n`lines` is a `map` which makes exact assertions on a given line by line number.\n\n - name: `lines`\n - type: `map`\n - default: `{}`\n - note: starts counting at `1` ;-)\n \n```yaml\necho test\\nline 2:\n  stdout:\n    lines:\n      2: line 2 # asserts only the second line\n```\n\n##### line-count\n\n`line-count` asserts the amount of lines printed to the output. If set to `0` this property is ignored.\n\n - name: `line-count`\n - type: `int`\n - default: `0`\n\n```yaml\necho test\\nline 2:\n  stdout:\n    line-count: 2\n```\n\n##### not-contains\n\n`not-contains` is a `array` of elements which are not allowed to be contained in the output. \nIt is the inversion of [contains](#contains).\n\n - name: `not-contains`\n - type: `array`\n - default: `[]`\n\n```yaml\necho hello:\n  stdout:\n    not-contains: bonjour # test passes because bonjour does not occur in the output\n \necho bonjour:\n  stdout:\n    not-contains: bonjour # test fails because bonjour occurs in the output\n```\n\n##### xml\n\n`xml` is a `map` which allows to query `xml` documents viá `xpath` queries. \nLike the [json][#json] assertion this uses the `key` of the map as the query parameter to, the `value` is the expected value.\n\n - name: `xml`\n - type: `map`\n - default: `{}`\n - notes: Used library [xmlquery](https://github.com/antchfx/xmlquery)\n\n```yaml\ncat some.xml:\n  stdout:\n    xml:\n      //book//author: J. R. R. Tolkien\n```\n\n`some.xml` file:\n\n```xml\n\u003cbook\u003e\n    \u003cauthor\u003eJ. R. R. Tolkien\u003c/author\u003e\n\u003c/book\u003e\n```\n\n##### file\n\n`file` is a file path, relative to the working directory that will have\nits entire contents matched against the command output. Other than\nreading from a file this works the same as [exactly](#exactly).\n\nThe example below will always pass.\n\n```yaml\noutput should match file:\n  command: cat output.txt\n  stdout:\n    file: output.txt\n```\n\n#### stderr\n\nSee [stdout](#stdout) for more information.\n\n - name: `stderr`\n - type: `string` or `map`\n - default: ` `\n - notes: is identical to [stdout](#stdout) \n\n```yaml\n# \u003e\u00262 echos directly to stderr\n\"\u003e\u00262 echo error\": \n  stderr: error\n  exit-code: 0\n\n\"\u003e\u00262 echo more errors\":\n  stderr:\n    line-count: 1\n```\n\n#### skip\n\n`skip` is a `boolean` type, setting this field to `true` will skip the test case.\n\n - name: `skip`\n - type: `bool`\n - default: `false`\n\n```yaml\necho test:\n  stdout: test\n  skip: true\n```\n\n### \u003ca name=\"config-config\"\u003e\u003c/a\u003eConfig\n\nYou can add configs which will be applied to all tests within a file or just for a specific test case, i.e.:\n\n```yaml\nconfig:\n  dir: /home/root # Set working directory for all tests\n\ntests:\n  echo hello:\n    config: # Define test specific configs which overwrite global configs\n      timeout: 5s\n  exit-code: 0\n```\n\nYou also have the option to define a separate config file that will\nbe applied globally to all test cases being ran using the `--config` flag.\n\nThe following will set the working directory for all tests that do not \nexplicitly set `config.dir` in the file config or the\ntest case config. In short, the lowest level config values takes precednce.\n\n```bash\n./commander test --config foo/bar/my-conifg.yaml foo/bar/test-suite.yaml\n```\n\n```yaml\n# foo/bar/my-conifg.yaml`\n\nconfig:\n  dir: /home/root # Set working directory for all tests\n```\n\n```yaml\n# foo/bar/test-suite.yaml\n\ntests:\n  echo hello:\n    config: # Define test specific configs which overwrite global configs\n      timeout: 5s\n  exit-code: 0\n```\n\n#### dir\n\n`dir` is a `string` which sets the current working directory for the command under test. \nThe test will fail if the given directory does not exist.\n\n - name: `dir`\n - type: `string`\n - default: `current working dir`\n\n```yaml\ndir: /home/root\n```\n\n#### env\n\n`env` is a `hash-map` which is used to set custom env variables. The `key` represents the variable name and the `value` setting the value of the env variable.\n\n - name: `env`  \n - type: `hash-map`\n - default: `{}`\n - notes:\n    - read env variables with `${PATH}`\n    - overwrites inherited variables, see [#inherit-env](#inherit-env) \n    \n```yaml\nenv:\n  VAR_NAME: my value # Set custom env var\n  CURRENT_USER: ${USER} # Set env var and read from current env\n```\n\n#### inherit-env\n\n`inherit-env` is a `boolean` type which allows you to inherit all environment variables from your active shell.\n\n - name: `inherit-env`\n - type: `bool`\n - default: `false`\n - notes: If this config is set to `true` in the global configuration it will be applied for all tests and ignores local test configs.\n\n```yaml\ninherit-env: true\n```\n\n#### interval\n\n`interval` is a `string` type and sets the `interval` between [retries](#retries).\n \n - name: `interval`\n - type: `string`\n - default: `0ns`\n - notes:\n   - valid time units: ns, us, µs, ms, s, m, h\n   - time string will be evaluated by golang's `time` package, further reading [time/#ParseDuration](https://golang.org/pkg/time/#ParseDuration)\n\n```yaml\ninterval: 5s # Waits 5 seconds until the next try after a failed test is started\n```\n\n#### retries\n\n`retries` is an `int` type and configures how often a test is allowed to fail until it will be marked as failed for the whole test run.\n\n - name: `retries`\n - type: `int`\n - default: `0`\n - notes: [interval](#interval) can be defined between retry executions\n\n```yaml\nretries: 3 # Test will be executed 3 times or until it succeeds\n```\n\n#### timeout\n\n`timeout` is a `string` type and sets the time a test is allowed to run. \nThe time is parsed from a duration string like `300ms`.\nIf a tests exceeds the given `timeout` the test will fail.\n\n - name: `timeout`\n - type: `string`\n - default: `no limit`\n - notes:\n   - valid time units: ns, us, µs, ms, s, m, h\n   - time string will be evaluated by golang's `time` package, further reading [time/#ParseDuration](https://golang.org/pkg/time/#ParseDuration)\n\n```yaml\ntimeout: 600s\n```\n\n### Nodes\n\n`Commander` has the option to execute tests against other hosts, i.e. via ssh.\n\nAvailable node types are currently:\n\n - `local`, execute tests locally\n - `ssh`, execute tests viá ssh\n - `docker`, execute tests inside a docker container\n\n```yaml\nnodes: # define nodes in the node section\n  ssh-host:\n    type: ssh # define the type of the connection \n    user: root # set the user which is used by the connection\n    pass: password # set password for authentication\n    addr: 192.168.0.100:2222 # target host address\n    identity-file: ~/.ssh/id_rsa # auth with private key\ntests:\n  echo hello:\n    config:\n      nodes: # define on which host the test should be executed\n        - ssh-host\n    stdout: hello\n    exit-code: 0\n```\n\nYou can identify on which node a test failed by inspecting the test output.\nThe `[local]` and `[ssh-host]` represent the node name on which the test were executed.\n\n```\n✗ [local] it should test ssh host\n✗ [ssh-host] it should fail if env could not be set\n```\n\n#### local\n\nThe `local` node is the default execution and will be applied if nothing else was configured.\nIt is always pre-configured and available, i.e. if you want to execute tests on a node and locally.\n\n```yaml\nnodes:\n  ssh-host:\n    addr: 192.168.1.100\n    user: ...\ntests:\n  echo hello:\n    config:\n      nodes: # will be executed on local and ssh-host\n        - ssh-host\n        - local\n    exit-code: 0\n```\n\n#### ssh\n\nThe `ssh` node type will execute tests against a configured node using ssh.\n\n**Limitations:** \n - The `inhereit-env` config is disabled for ssh hosts, nevertheless it is possible to set env variables\n - Private registries are not supported at the moment\n\n```yaml\nnodes: # define nodes in the node section\n  ssh-host:\n    type: ssh # define the type of the connection \n    user: root # set the user which is used by the connection\n    pass: password # set password for authentication\n    addr: 192.168.0.100:2222 # target host address\n    identity-file: ~/.ssh/id_rsa # auth with private key\ntests:\n  echo hello:\n    config:\n      nodes: # define on which host the test should be executed\n        - ssh-host\n    stdout: hello\n    exit-code: 0\n```\n\n#### docker\n\nThe `docker` node type executes the given command inside a docker container.\n\n**Notes:** If the default docker registry should be used prefix the container with the registry `docker.io/library/` \n\n```yaml:\nnodes:\n  docker-host:\n    type: docker\n    image: docker.io/library/alpine:3.11.3\n    docker-exec-user: 1000 # define the owner of the executed command\n    user: user # registry user\n    pass: password # registry password, it is recommended to use env variables like $REGISTRY_PASS\nconfig:\n  nodes:\n    - docker-host\n    \ntests:\n  \"id -u\":\n     stdout: \"1001\"\n```\n\n### Development\n\nSee the documentation at [development.md](docs/development.md)\n\n## Misc\n\nHeavily inspired by [goss](https://github.com/aelsabbahy/goss).\n\nSimilar projects:\n - [bats](https://github.com/sstephenson/bats)\n - [icmd](https://godoc.org/gotest.tools/icmd)\n - [testcli](https://github.com/rendon/testcli)\n","funding_links":[],"categories":["Testing Frameworks","Testing"],"sub_categories":["Testing Frameworks"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcommander-cli%2Fcommander","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcommander-cli%2Fcommander","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcommander-cli%2Fcommander/lists"}