├── .github ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.md │ └── FEATURE_REQUEST.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci-workflow.yml │ └── release-workflow.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── docs ├── images │ └── preview.png └── release_notes │ ├── v0.1.0.md │ ├── v0.2.0.md │ ├── v0.3.0.md │ ├── v0.4.0.md │ ├── v0.5.0.md │ ├── v0.6.0.md │ ├── v0.7.0.md │ └── v0.8.0.md ├── entrypoint.sh ├── src ├── install_shellcheck.sh ├── tagging.sh └── version ├── test_data ├── exclude_issues │ ├── test_script_exclude_multiple_errors.sh │ ├── test_script_exclude_none.sh │ └── test_script_exclude_one_error.sh ├── script_type │ ├── sample.bash │ ├── script_with_invalid_shell_directive │ ├── script_with_valid_shell_directive │ ├── test.zsh │ ├── test_python │ ├── test_script.js │ ├── test_script_wosh.sh │ ├── test_script_wsh.sh │ └── test_zsh_wsh ├── severity_mode │ ├── test_script_error.sh │ ├── test_script_info.sh │ ├── test_script_no_error.sh │ ├── test_script_style.sh │ └── test_script_warning.sh └── test_dir │ ├── example_script.sh │ ├── executable_script │ ├── external_sources.sh │ └── invalid_script └── tests ├── integration_tests ├── exclude_issues_tests.sh ├── ignored_path_tests.sh ├── input_path_tests.sh └── severity_mode_tests.sh ├── shunit2 ├── test_runner └── unit_tests └── scan_tests.sh /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug or something that is not working as expected 4 | 5 | --- 6 | 7 | ## New Issue Checklist 8 | 9 | - [ ] Using the latest version of Azbagheri/shell-linter 10 | - [ ] Able to reproduce error multiple times 11 | - [ ] Provided information about the bug 12 | - [ ] Attached additional information about the error including logs 13 | 14 | ### Issue Description 15 | 16 | ``` 17 | 18 | ``` 19 | 20 | ### Steps to Reproduce 21 | 22 | ``` 23 | 24 | ``` 25 | 26 | ### Error Message 27 | 28 | ``` 29 | 30 | ``` 31 | 32 | ### Logs 33 | 34 | ``` 35 | 36 | ``` 37 | 38 | ### Link to the GitHub Actions workflow (optional) 39 | 40 | ``` 41 | 42 | ``` 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: A suggestion for a new feature 4 | 5 | --- 6 | 7 | ## Feature Request 8 | 9 | ### Motivation Behind Feature 10 | 11 | ``` 12 | 13 | ``` 14 | 15 | ### Feature Description 16 | 17 | ``` 18 | 19 | ``` 20 | 21 | ### Current Alternatives or Workarounds 22 | 23 | ``` 24 | 25 | ``` 26 | 27 | ### Additional Context 28 | 29 | ``` 30 | 31 | ``` 32 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Pull Request Checklist 2 | - [ ] I have created an issue prior to creating this pull request 3 | - [ ] I have provided a detailed description and motivation regarding the change below 4 | - [ ] I have updated the test suite and documentation to support my change 5 | 6 | ### Corresponding Issue 7 | 8 | ``` 9 | 10 | ``` 11 | 12 | ### Description of Change 13 | 14 | ``` 15 | 16 | ``` 17 | 18 | ### Motivation and Context 19 | 20 | ``` 21 | 22 | ``` 23 | 24 | ### Testing Steps 25 | 26 | ``` 27 | 28 | ``` 29 | 30 | ### Risks 31 | 32 | ``` 33 | 34 | ``` 35 | 36 | ### Additional Information 37 | 38 | ``` 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /.github/workflows/ci-workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI Pipeline 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - develop 8 | 9 | jobs: 10 | run_tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | - name: Install Shellcheck 16 | run: ./src/install_shellcheck.sh 17 | - name: Run tests 18 | run: ./tests/test_runner 19 | shell: bash 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/release-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | github_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Get version 16 | id: app-version 17 | run: echo ::set-output name=VERSION::$(./src/version) 18 | 19 | - name: Create tag and release 20 | uses: ncipollo/release-action@v1 21 | with: 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | tag: ${{ steps.app-version.outputs.VERSION }} 24 | commit: main 25 | name: Shell Linter ${{ steps.app-version.outputs.VERSION }} 26 | bodyFile: ./docs/release_notes/v0.8.0.md 27 | 28 | - name: Update latest tag 29 | run: ./src/tagging.sh ${{ secrets.GITHUB_TOKEN }} 30 | 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG SHELLCHECK_VERSION=v0.10.0 2 | FROM koalaman/shellcheck:${SHELLCHECK_VERSION} AS builder 3 | 4 | FROM alpine:3.20.3 5 | 6 | COPY --from=builder /bin/shellcheck /usr/local/bin/ 7 | 8 | RUN apk update && apk add --no-cache bash && \ 9 | shellcheck --version && \ 10 | bash --version 11 | 12 | COPY entrypoint.sh /entrypoint.sh 13 | 14 | ENTRYPOINT ["/entrypoint.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Azadeh Bagheri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shell Linter 2 | 3 | [![Release](https://img.shields.io/github/release/Azbagheri/shell-linter.svg)](https://github.com/Azbagheri/shell-linter/releases) 4 | [![Marketplace](https://img.shields.io/badge/GitHub-Marketplace-red.svg)](https://github.com/marketplace/actions/shell-linter) 5 | [![Actions Status](https://github.com/Azbagheri/shell-linter/workflows/CI-workflow/badge.svg)](https://github.com/Azbagheri/shell-linter/actions?query=branch%3Adevelop) 6 | 7 | 8 | A GitHub Action that performs static analysis for shell scripts using [ShellCheck](https://github.com/koalaman/shellcheck). 9 | 10 | ![](docs/images/preview.png) 11 | 12 | ## 🚨 Repository Transferred – Manual Update Required 13 | 14 | This repository has moved from **`azohra/shell-linter`** to **`Azbagheri/shell-linter`**. 15 | 16 | ❗ **Important:** Contrary to our understanding, GitHub is NOT automatically redirecting workflows. You must manually update your YAML configuration: 17 | 18 | ```diff 19 | - uses: azohra/shell-linter@latest 20 | + uses: Azbagheri/shell-linter@latest 21 | ``` 22 | 23 | We sincerely apologize for any inconvenience this has caused. 24 |
25 | 26 | # Usage 27 | 28 | Shell Linter can perform static analysis in various ways. By default it scans all the ShellCheck-supported shell scripts (sh, bash, dash, ksh) in your project. However, you can use the `path` parameter to scan a specific file or folder or use the `exclude-paths` parameter to exclude files or folders from the scan. With Shell Linter, you can also specify the minimum severity of errors to consider using the `severity` parameter. Specific use cases along with examples are shown below: 29 | 30 | #### Run static analysis for all the supported shell scripts in your repository: 31 | ```yml 32 | jobs: 33 | lint: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Checkout code 37 | uses: actions/checkout@v4 38 | - name: Run ShellCheck 39 | uses: Azbagheri/shell-linter@latest 40 | ``` 41 | 42 | #### Run static analysis for a single shell script: 43 | ```yml 44 | - name: Run ShellCheck 45 | uses: Azbagheri/shell-linter@latest 46 | with: 47 | path: "setup.sh" 48 | ``` 49 | 50 | #### Run static analysis for multiple shell scripts **with or without** extension: 51 | ```yml 52 | - name: Run ShellCheck 53 | uses: Azbagheri/shell-linter@latest 54 | with: 55 | path: "setup,deploy.sh" 56 | ``` 57 | 58 | #### Run static analysis for all the shell scripts in a folder: 59 | ```yml 60 | - name: Run ShellCheck 61 | uses: Azbagheri/shell-linter@latest 62 | with: 63 | path: "src" 64 | ``` 65 | 66 | #### Run static analysis using a **wildcard** path: 67 | ```yml 68 | - name: Run ShellCheck 69 | uses: Azbagheri/shell-linter@latest 70 | with: 71 | path: "src/*.sh" 72 | ``` 73 | #### Exclude files and folders from the static analysis: 74 | ```yml 75 | - name: Run ShellCheck 76 | uses: Azbagheri/shell-linter@latest 77 | with: 78 | exclude-paths: "src/setup.sh,tests/unit_tests" 79 | ``` 80 | Note that `exclude-paths` only accepts paths relative to your project's root directory. However, **do not** include `./` at the beginning of the paths. 81 | 82 | To exclude a folder and it's content recursively just provide the path of the folder **without** a `/` at the end. In the example above, the entire folder at the path `tests/unit_tests` will be excluded from linting. 83 | 84 | #### Run static analysis for all the shell scripts and only report issues with error severity while excluding specific issues: 85 | ```yml 86 | - name: Run ShellCheck 87 | uses: Azbagheri/shell-linter@latest 88 | with: 89 | path: "src/*.sh" 90 | severity: "error" 91 | exclude-issues: "SC1068,SC1066" 92 | ``` 93 | Note that `exclude-issues` contains a comma-separated list of ShellCheck issues (example: "SC1068") to ignore. 94 | 95 | #### Run analysis by using a specific version of Shell Linter: 96 | ```yml 97 | - name: Run ShellCheck 98 | uses: Azbagheri/shell-linter@v0.8.0 99 | ``` 100 | 101 | # Input 102 | 103 | ### `exclude-issues` 104 | Optional. Specify shellcheck issues to exclude during scan. For more information refer to [Checks](https://github.com/koalaman/shellcheck/wiki/Checks). Default: scan all issues. 105 | 106 | ### `exclude-paths` 107 | Optional. Exclude files and folders from ShellCheck scan. 108 | 109 | ### `path` 110 | Optional. Execute lint check on a specific file or folder. Default: `.` 111 | 112 | ### `severity` 113 | Optional. Specify minimum severity of errors to consider [style, info, warning, error]. Default: `style` 114 | 115 | # License 116 | This software is available as open source under the terms of the MIT License. 117 | 118 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Shell Linter' 2 | description: 'Execute lint check on shell scripts using ShellCheck' 3 | author: 'Azadeh Bagheri' 4 | inputs: 5 | path: 6 | description: 'Execute lint check on a specific file or folder.' 7 | required: false 8 | default: '.' 9 | severity: 10 | description: 'Specify minimum severity of errors to consider.' 11 | required: false 12 | default: 'style' 13 | exclude-paths: 14 | description: 'Specify files or folders to exclude during scan.' 15 | required: false 16 | default: '' 17 | exclude-issues: 18 | description: 'Specify shellcheck issues to exclude during scan.' 19 | required: false 20 | default: '' 21 | runs: 22 | using: 'docker' 23 | image: 'Dockerfile' 24 | args: 25 | - ${{ inputs.path }} 26 | - ${{ inputs.severity }} 27 | - ${{ inputs.exclude-paths}} 28 | - ${{ inputs.exclude-issues }} 29 | branding: 30 | icon: 'check-circle' 31 | color: 'green' 32 | -------------------------------------------------------------------------------- /docs/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azbagheri/shell-linter/1e31b7f51d4b172285ff6b0933199f93779c4811/docs/images/preview.png -------------------------------------------------------------------------------- /docs/release_notes/v0.1.0.md: -------------------------------------------------------------------------------- 1 | 2 | First release -------------------------------------------------------------------------------- /docs/release_notes/v0.2.0.md: -------------------------------------------------------------------------------- 1 | 2 | Added support for scripts with no extension. -------------------------------------------------------------------------------- /docs/release_notes/v0.3.0.md: -------------------------------------------------------------------------------- 1 | 2 | Locked shellcheck version used in the shell-linter action to v0.7.0 instead of latest. This allows for more control over the state of the action by avoiding possible instabilities and dependency issues that may come with latest versions. -------------------------------------------------------------------------------- /docs/release_notes/v0.4.0.md: -------------------------------------------------------------------------------- 1 | - Added support for ShellCheck severity mode 2 | - The default behavior is set to `style` which considers errors with all severity levels 3 | - Improved tagging 4 | - The latest stable version can be fetched by using `@latest` -------------------------------------------------------------------------------- /docs/release_notes/v0.5.0.md: -------------------------------------------------------------------------------- 1 | * Improved performance by only scanning the Shellcheck-supported scripts (sh/bash/dsh/ksh) 2 | * Added templates for bug reports, feature requests and pull requests 3 | * Improved code coverage by adding more unit tests and integration tests -------------------------------------------------------------------------------- /docs/release_notes/v0.6.0.md: -------------------------------------------------------------------------------- 1 | - Added support for excluding files and folders from Shellcheck scan 2 | - Upgraded the Shellcheck version used in the Shell-linter action to v0.7.2 to support the updated error/warning codes 3 | - Improved performance by adding `-x` to shellcheck to follow sourced files that are not specified as input -------------------------------------------------------------------------------- /docs/release_notes/v0.7.0.md: -------------------------------------------------------------------------------- 1 | ## New Features and Improvements 2 | - Added support for excluding specific ShellCheck issues from static analysis, allowing users to suppress warnings or errors that are not relevant to their workflow. 3 | - Improved the Shellcheck's installation process by pulling the binary from its official Docker image using a multi-stage build. 4 | 5 | ## Ownership Change Notice 6 | 7 | This is the first release after the ownership of this repository has changed. For important updates and instructions, please refer to the README file. If you have any questions or concerns regarding the action, feel free to open an issue. Thank you for your continued support! -------------------------------------------------------------------------------- /docs/release_notes/v0.8.0.md: -------------------------------------------------------------------------------- 1 | ## New Features 2 | - **Support for ShellCheck Shell Directive**: Added functionality to handle scripts that include the `# shellcheck shell=` directive. -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | # shellcheck disable=SC2155 3 | 4 | input_paths="$1" 5 | severity_mode="$2" 6 | exclude_paths="$3" 7 | exclude_issues="$4" 8 | execution_mode="$5" 9 | my_dir=$(pwd) 10 | status_code="0" 11 | find_path_clauses=(! -path "${my_dir}/.git/*") 12 | invalid_files=() 13 | shebang_regex="^#!.*[/ ](sh|bash|dash|ksh)$" 14 | second_line_regex="^#[[:blank:]]*shellcheck[[:blank:]]+shell=(sh|bash|dash|ksh)[[:blank:]]*$" 15 | 16 | process_input(){ 17 | [ -n "$execution_mode" ] && my_dir="./test_data" 18 | 19 | severity_mode="$(echo $severity_mode | tr '[:upper:]' '[:lower:]')" 20 | 21 | if [[ "$severity_mode" != "style" && "$severity_mode" != "info" && "$severity_mode" != "warning" && "$severity_mode" != "error" ]]; then 22 | echo "Warning: unknown severity mode. Defaulting severity mode to style." 23 | severity_mode="style" 24 | fi 25 | 26 | if [ -n "$exclude_paths" ]; then 27 | for path in $(echo "$exclude_paths" | tr "," "\n"); do 28 | if [ -d "${my_dir}/$path" ]; then 29 | find_path_clauses+=( ! -path "${my_dir}/$path/*") 30 | else 31 | find_path_clauses+=( ! -path "${my_dir}/$path" ) 32 | fi 33 | done 34 | fi 35 | 36 | optional_params="" 37 | if [[ -n "$exclude_issues" ]]; then 38 | optional_params="--exclude $exclude_issues" 39 | fi 40 | 41 | if [[ -n "$input_paths" && "$input_paths" != "." ]]; then 42 | for path in $(echo "$input_paths" | tr "," "\n"); do 43 | if [ -d "$path" ]; then 44 | scan_dir "$path" 45 | else 46 | scan_file "$path" 47 | fi 48 | done 49 | [[ ${#invalid_files[@]} -gt 0 ]] && log_invalid_files 50 | [ -z "$execution_mode" ] && exit $status_code 51 | else 52 | scan_dir "$my_dir" 53 | [[ ${#invalid_files[@]} -gt 0 ]] && log_invalid_files 54 | [ -z "$execution_mode" ] && exit $status_code 55 | fi 56 | } 57 | 58 | scan_file(){ 59 | local file_path=$1 60 | local first_line=$(head -n 1 "$file_path") 61 | 62 | if [[ "$first_line" =~ $shebang_regex ]]; then 63 | run_shellcheck $file_path 64 | else 65 | local second_line=$(sed -n '2p' "$file_path") 66 | if [[ "$second_line" =~ $second_line_regex ]]; then 67 | run_shellcheck $file_path 68 | else 69 | invalid_files+=( $file_path ) 70 | fi 71 | fi 72 | } 73 | 74 | scan_dir(){ 75 | echo "Scanning all the shell scripts at $1 🔎" 76 | while IFS= read -r script 77 | do 78 | scan_file "$script" 79 | done < <(find "$1" -type f \( -iname '*.sh' -o -iname '*.bash' -o -iname '*.ksh' -o ! -iname '*.*' \) "${find_path_clauses[@]}") 80 | } 81 | 82 | run_shellcheck(){ 83 | local file_path=$1 84 | local file=$(basename -- "$file_path") 85 | echo 86 | echo "###############################################" 87 | echo " Scanning $file" 88 | echo "###############################################" 89 | shellcheck -x "$file_path" --severity="$severity_mode" $optional_params 90 | local exit_code=$? 91 | if [ $exit_code -eq 0 ] ; then 92 | printf "%b" "Successfully scanned ${file_path} 🙌\n" 93 | else 94 | status_code=$exit_code 95 | printf "\e[31m ERROR: ShellCheck detected issues in %s.\e[0m\n" "${file_path} 🐛" 96 | fi 97 | } 98 | 99 | # Logging files with no extension that are not amongst the supported scripts or scripts that are supported but don't have a shebang. 100 | log_invalid_files(){ 101 | printf "\n\e[33m ⚠️ Found %d unscanned files that could potentially be supported: \e[0m\n" "${#invalid_files[@]}" 102 | for file in ${invalid_files[@]}; do 103 | printf "\n\t\e[33m %s \e[0m\n" "$file" 104 | done 105 | printf "\n\e[33m ShellCheck only supports sh, bash, dash, and ksh scripts. To ensure your script is scanned correctly, add a proper shebang on the first line or a shell directive on the second line. For more details, see: https://www.shellcheck.net/wiki/SC1008 \n" 106 | 107 | printf "\n\e[33m To fix the warning for unsupported scripts or to ignore specific files, use the 'exclude-paths' input. For more information, check: 108 | https://github.com/Azbagheri/shell-linter#input\e[0m\n" 109 | } 110 | 111 | # To avoid execution when sourcing this script for testing 112 | [ "$0" = "${BASH_SOURCE[0]}" ] && process_input 113 | -------------------------------------------------------------------------------- /src/install_shellcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | scversion='v0.10.0' 4 | 5 | wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${scversion?}/shellcheck-${scversion?}.linux.x86_64.tar.xz" | tar -xJv 6 | cp "shellcheck-${scversion}/shellcheck" /usr/local/bin 7 | rm -rf "shellcheck-${scversion}" 8 | shellcheck --version -------------------------------------------------------------------------------- /src/tagging.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | token=$1 4 | repo_owner="Azbagheri" 5 | repo_name="shell-linter" 6 | tag_name="latest" 7 | commit_sha=$(git log -n1 --format=format:"%H") 8 | 9 | create_tag(){ 10 | command="curl -i \ 11 | --request POST \ 12 | -u username:$token \ 13 | --data '{\"ref\": \"refs/tags/$tag_name\",\"sha\": \"$commit_sha\"}' \ 14 | https://api.github.com/repos/${repo_owner}/${repo_name}/git/refs" 15 | 16 | git tag $tag_name 17 | eval "$command" 18 | } 19 | 20 | update_tag() { 21 | command="curl -i \ 22 | --request PATCH \ 23 | -u username:$token \ 24 | --data '{\"sha\": \"$commit_sha\", \"force\":true}' \ 25 | https://api.github.com/repos/${repo_owner}/${repo_name}/git/refs/tags/$tag_name" 26 | 27 | eval "$command" 28 | } 29 | 30 | if git tag --list | grep -Eq "$tag_name" ; then 31 | echo "Updating the 'latest' tag" 32 | update_tag 33 | else 34 | echo "Creating the 'latest' tag" 35 | create_tag 36 | fi -------------------------------------------------------------------------------- /src/version: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | VERSION="v0.8.0" 4 | echo $VERSION -------------------------------------------------------------------------------- /test_data/exclude_issues/test_script_exclude_multiple_errors.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | var=World; echo "Hello " 4 | echo "$(date)" 5 | 6 | $foo=42 7 | -------------------------------------------------------------------------------- /test_data/exclude_issues/test_script_exclude_none.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | echo "Hello $name" 4 | -------------------------------------------------------------------------------- /test_data/exclude_issues/test_script_exclude_one_error.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | var = 42 3 | echo -n 42 4 | -------------------------------------------------------------------------------- /test_data/script_type/sample.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | -------------------------------------------------------------------------------- /test_data/script_type/script_with_invalid_shell_directive: -------------------------------------------------------------------------------- 1 | #!/bin/mywrapper 2 | # shellcheck shell=zsh 3 | 4 | echo "Hello World" -------------------------------------------------------------------------------- /test_data/script_type/script_with_valid_shell_directive: -------------------------------------------------------------------------------- 1 | #!/bin/mywrapper 2 | # shellcheck shell=bash 3 | 4 | echo "Hello World" -------------------------------------------------------------------------------- /test_data/script_type/test.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | echo "hello" -------------------------------------------------------------------------------- /test_data/script_type/test_python: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | print("hello, world!") -------------------------------------------------------------------------------- /test_data/script_type/test_script.js: -------------------------------------------------------------------------------- 1 | // hello.js 2 | function hello(string) { 3 | return 'hello ' + string; 4 | } -------------------------------------------------------------------------------- /test_data/script_type/test_script_wosh.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azbagheri/shell-linter/1e31b7f51d4b172285ff6b0933199f93779c4811/test_data/script_type/test_script_wosh.sh -------------------------------------------------------------------------------- /test_data/script_type/test_script_wsh.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | -------------------------------------------------------------------------------- /test_data/script_type/test_zsh_wsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh -------------------------------------------------------------------------------- /test_data/severity_mode/test_script_error.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Error 4 | if (( $n > 3 )) 5 | -------------------------------------------------------------------------------- /test_data/severity_mode/test_script_info.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | cd dir; process *; cd ..; 4 | -------------------------------------------------------------------------------- /test_data/severity_mode/test_script_no_error.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Error 4 | if (( $n > 3 )); then 5 | echo "larger" 6 | fi 7 | -------------------------------------------------------------------------------- /test_data/severity_mode/test_script_style.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | echo "$(date)" 4 | -------------------------------------------------------------------------------- /test_data/severity_mode/test_script_warning.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Warning 4 | [[ $1 == $result ]] 5 | 6 | # Info 7 | cd dir; process *; cd ..; 8 | 9 | # Style 10 | [[ -z $(find /tmp | grep mpg) ]] 11 | -------------------------------------------------------------------------------- /test_data/test_dir/example_script.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | echo "hello, world" -------------------------------------------------------------------------------- /test_data/test_dir/executable_script: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | -------------------------------------------------------------------------------- /test_data/test_dir/external_sources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -f ./src/tagging.sh ]]; then 4 | . /src/tagging.sh 5 | fi -------------------------------------------------------------------------------- /test_data/test_dir/invalid_script: -------------------------------------------------------------------------------- 1 | 2 | echo "hello world" -------------------------------------------------------------------------------- /tests/integration_tests/exclude_issues_tests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | source ./entrypoint.sh "" "" "" "" "--test" 4 | 5 | test_exclude_no_error(){ 6 | input_paths="./test_data/exclude_issues/test_script_exclude_none.sh" 7 | severity_mode="style" 8 | exclude_issues="" 9 | local expected_error="SC2154" 10 | local actual_message=$(process_input) 11 | 12 | assertContains "Actual messages:$actual_message Did not find the message.\n" "$actual_message" "$expected_error" 13 | } 14 | 15 | test_exclude_one_error(){ 16 | input_paths="./test_data/exclude_issues/test_script_exclude_one_error.sh" 17 | severity_mode="style" 18 | exclude_issues="SC2283" 19 | local expected_error="SC3037" 20 | local not_expected_error="SC2283" 21 | local actual_message=$(process_input) 22 | 23 | assertContains "Actual messages:$actual_message Did not find the message.\n" "$actual_message" "$expected_error" 24 | assertNotContains "Actual messages:$actual_message contains the message.\n" "$actual_message" "$not_expected_error" 25 | } 26 | 27 | test_exclude_multiple_errors(){ 28 | input_paths="./test_data/exclude_issues/test_script_exclude_multiple_errors.sh" 29 | severity_mode="style" 30 | exclude_issues="SC1017,SC2281,SC2034,SC2154,SC2005" 31 | local expected_message="Successfully scanned" 32 | local actual_message=$(process_input) 33 | 34 | assertContains "Actual messages:$actual_message Did not find the message.\n" "$actual_message" "$expected_message" 35 | } 36 | 37 | source ./tests/shunit2 38 | -------------------------------------------------------------------------------- /tests/integration_tests/ignored_path_tests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # shellcheck disable=SC2155 3 | 4 | source ./entrypoint.sh "" "" "" "" "--test" 5 | 6 | test_ignore_directories(){ 7 | local exclude_paths="test_dir,severity_mode" 8 | local actual_message=$(process_input) 9 | local message1="Scanning example_script.sh" 10 | local message2="Scanning test_script_info.sh" 11 | local expected1="Scanning test_script_wsh.sh" 12 | 13 | assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$message1" 14 | assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$message2" 15 | assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$expected1" 16 | } 17 | 18 | test_ignore_files(){ 19 | local input_paths="./test_data/script_type" 20 | local exclude_paths="script_type/test.zsh,script_type/test_script.js,script_type/test_zsh_wsh,script_type/test_python,script_type/test_script_wosh.sh" 21 | local actual_message=$(process_input) 22 | local expected1="Scanning sample.bash" 23 | local expected2="Scanning test_script_wsh.sh" 24 | local notExpected1="Scanning test_script.js" 25 | local notExpected2="Scanning test_python" 26 | 27 | assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$expected1" 28 | assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$expected2" 29 | assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$notExpected1" 30 | assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$notExpected2" 31 | } 32 | 33 | test_ignore_file_and_directory(){ 34 | local exclude_paths="script_type,severity_mode,test_dir/invalid_script" 35 | local actual_message=$(process_input) 36 | local expected1="Scanning example_script.sh" 37 | local expected2="Scanning executable_script" 38 | local notExpected1="Scanning invalid_script" 39 | local notExpected2="Scanning test_script_error" 40 | 41 | assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$expected1" 42 | assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$expected2" 43 | assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$notExpected1" 44 | assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$notExpected2" 45 | } 46 | 47 | tearDown(){ 48 | input_paths="" 49 | invalid_files=() 50 | } 51 | source ./tests/shunit2 52 | -------------------------------------------------------------------------------- /tests/integration_tests/input_path_tests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # shellcheck disable=SC2155 3 | 4 | source ./entrypoint.sh "" "" "" "" "--test" 5 | 6 | test_execution_mode(){ 7 | local expected_path=./test_data 8 | process_input > /dev/null 9 | local actual_path=$my_dir 10 | 11 | assertEquals "ERROR: Values did not match." "$expected_path" "$actual_path" 12 | } 13 | 14 | test_invalid_script_with_extension(){ 15 | input_paths="./test_data/script_type/test_script.js" 16 | local expected1="Found 1 unscanned files that could potentially be supported" 17 | local expected2="ShellCheck only supports sh, bash, dash, and ksh scripts. To ensure your script is scanned correctly, add a proper shebang on the first line or a shell directive on the second line." 18 | local actual=$(process_input) 19 | 20 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected1" 21 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected2" 22 | } 23 | 24 | test_invalid_script_without_extension(){ 25 | input_paths="./test_data/script_type/test_python" 26 | local expected1="Found 1 unscanned files that could potentially be supported" 27 | local expected2="ShellCheck only supports sh, bash, dash, and ksh scripts. To ensure your script is scanned correctly, add a proper shebang on the first line or a shell directive on the second line." 28 | local actual=$(process_input) 29 | 30 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected1" 31 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected2" 32 | } 33 | 34 | test_unsupported_script_without_extension(){ 35 | input_paths="./test_data/script_type/test_zsh_wsh" 36 | local expect1="Found 1 unscanned files that could potentially be supported" 37 | local expect2="ShellCheck only supports sh, bash, dash, and ksh scripts. To ensure your script is scanned correctly, add a proper shebang on the first line or a shell directive on the second line." 38 | local actual=$(process_input) 39 | 40 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expect1" 41 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expect2" 42 | } 43 | 44 | test_unsupported_script_with_extension(){ 45 | input_paths="./test_data/script_type/test.zsh" 46 | local expect1="Found 1 unscanned files that could potentially be supported" 47 | local expect2="ShellCheck only supports sh, bash, dash, and ksh scripts. To ensure your script is scanned correctly, add a proper shebang on the first line or a shell directive on the second line." 48 | local actual=$(process_input) 49 | 50 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expect1" 51 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expect2" 52 | } 53 | 54 | test_valid_file_without_shebang(){ 55 | input_paths="./test_data/script_type/test_script_wosh.sh" 56 | local expected1="Found 1 unscanned files that could potentially be supported" 57 | local expected2="ShellCheck only supports sh, bash, dash, and ksh scripts. To ensure your script is scanned correctly, add a proper shebang on the first line or a shell directive on the second line." 58 | local actual=$(process_input) 59 | 60 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected1" 61 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected2" 62 | } 63 | 64 | test_valid_file_input(){ 65 | input_paths="./test_data/test_dir/example_script.sh" 66 | local expected="Scanning example_script.sh" 67 | local actual=$(process_input) 68 | 69 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected" 70 | } 71 | 72 | test_default_input_file(){ 73 | input_paths="." 74 | local expected_expect="Scanning all the shell scripts at ./test_data" 75 | local actual=$(process_input) 76 | 77 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected_expect" 78 | } 79 | 80 | test_input_directory(){ 81 | input_paths="./test_data/test_dir" 82 | local expect1="Scanning all the shell scripts at ./test_data/test_dir" 83 | local expect2="Scanning example_script.sh" 84 | local expect3="Scanning executable_script" 85 | local expect4="Found 1 unscanned files that could potentially be supported" 86 | local actual=$(process_input) 87 | 88 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expect1" 89 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expect2" 90 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expect3" 91 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expect4" 92 | } 93 | 94 | test_multiple_input_directories(){ 95 | input_paths="./test_data/test_dir,./test_data/script_type" 96 | local expected1="Scanning all the shell scripts at ./test_data/test_dir" 97 | local expected2="Scanning all the shell scripts at ./test_data/script_type" 98 | local actual=$(process_input) 99 | 100 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expect1" 101 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected2" 102 | } 103 | 104 | test_input_files_with_wildcard() { 105 | input_paths="./test_data/script_type/test*.sh" 106 | local expected1="Scanning test_script_wsh.sh" 107 | local expected2="Found 1 unscanned files that could potentially be supported" 108 | local actual=$(process_input) 109 | 110 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected1" 111 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected2" 112 | } 113 | 114 | test_script_with_invalid_shell_directive(){ 115 | local input_paths="./test_data/script_type/script_with_invalid_shell_directive" 116 | local not_expected="Scanning script_with_invalid_shell_directive" 117 | local expected="ShellCheck only supports sh, bash, dash, and ksh scripts. To ensure your script is scanned correctly, add a proper shebang on the first line or a shell directive on the second line." 118 | local actual=$(process_input) 119 | 120 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected" 121 | assertNotContains "Actual messages:$actual\n contains the unexpected message: '$not_expected'\n" "$actual" "$not_expected" 122 | } 123 | 124 | tearDown(){ 125 | input_paths="" 126 | invalid_files=() 127 | } 128 | source ./tests/shunit2 -------------------------------------------------------------------------------- /tests/integration_tests/severity_mode_tests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | source ./entrypoint.sh "" "" "" "" "--test" 4 | 5 | test_severity_mode_invalid(){ 6 | input_paths="./test_data/severity_mode/test_script_warning.sh" 7 | severity_mode="verbose" 8 | local expected_message1="Warning: unknown severity mode. Defaulting severity mode to style." 9 | local expected_message2="SC2143" 10 | local actual_message=$(process_input) 11 | 12 | assertContains "Did not find the message." "$actual_message" "$expected_message1" 13 | assertContains "Did not find the message." "$actual_message" "$expected_message2" 14 | } 15 | 16 | test_severity_mode_error(){ 17 | input_paths="./test_data/severity_mode/test_script_error.sh" 18 | severity_mode="error" 19 | local expected_message="SC1050" 20 | local actual_message=$(process_input) 21 | 22 | assertContains "Did not find the message." "$actual_message" "$expected_message" 23 | } 24 | 25 | test_severity_mode_no_error(){ 26 | input_paths="./test_data/severity_mode/test_script_no_error.sh" 27 | severity_mode="error" 28 | local expected_message="Successfully scanned" 29 | local actual_message=$(process_input) 30 | 31 | assertContains "Did not find the message." "$actual_message" "$expected_message" 32 | } 33 | 34 | test_severity_mode_warning(){ 35 | input_paths="./test_data/severity_mode/test_script_warning.sh" 36 | severity_mode="warning" 37 | local expected_message="SC2053" 38 | local actual_message=$(process_input) 39 | 40 | assertContains "Did not find the message." "$actual_message" "$expected_message" 41 | } 42 | 43 | test_severity_mode_info(){ 44 | input_paths="./test_data/severity_mode/test_script_info.sh" 45 | severity_mode="info" 46 | local expected_message="SC2035" 47 | local actual_message=$(process_input) 48 | 49 | assertContains "Did not find the message." "$actual_message" "$expected_message" 50 | } 51 | 52 | test_severity_mode_style(){ 53 | input_paths="./test_data/severity_mode/test_script_style.sh" 54 | severity_mode="style" 55 | local expected_message="SC2005" 56 | local actual_message=$(process_input) 57 | 58 | assertContains "Did not find the message." "$actual_message" "$expected_message" 59 | } 60 | 61 | source ./tests/shunit2 62 | -------------------------------------------------------------------------------- /tests/shunit2: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # Copyright 2008-2019 Kate Ward. All Rights Reserved. 5 | # Released under the Apache 2.0 license. 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # shUnit2 -- Unit testing framework for Unix shell scripts. 9 | # https://github.com/kward/shunit2 10 | # 11 | # Author: kate.ward@forestent.com (Kate Ward) 12 | # 13 | # shUnit2 is a xUnit based unit test framework for Bourne shell scripts. It is 14 | # based on the popular JUnit unit testing framework for Java. 15 | # 16 | # $() are not fully portable (POSIX != portable). 17 | # shellcheck disable=SC2006 18 | # expr may be antiquated, but it is the only solution in some cases. 19 | # shellcheck disable=SC2003 20 | 21 | # Return if shunit2 already loaded. 22 | command [ -n "${SHUNIT_VERSION:-}" ] && exit 0 23 | SHUNIT_VERSION='2.1.8pre' 24 | 25 | # Return values that scripts can use. 26 | SHUNIT_TRUE=0 27 | SHUNIT_FALSE=1 28 | SHUNIT_ERROR=2 29 | 30 | # Logging functions. 31 | _shunit_warn() { 32 | ${__SHUNIT_CMD_ECHO_ESC} \ 33 | "${__shunit_ansi_yellow}shunit2:WARN${__shunit_ansi_none} $*" >&2 34 | } 35 | _shunit_error() { 36 | ${__SHUNIT_CMD_ECHO_ESC} \ 37 | "${__shunit_ansi_red}shunit2:ERROR${__shunit_ansi_none} $*" >&2 38 | } 39 | _shunit_fatal() { 40 | ${__SHUNIT_CMD_ECHO_ESC} \ 41 | "${__shunit_ansi_red}shunit2:FATAL${__shunit_ansi_none} $*" >&2 42 | exit ${SHUNIT_ERROR} 43 | } 44 | 45 | # Determine some reasonable command defaults. 46 | __SHUNIT_CMD_ECHO_ESC='echo -e' 47 | # shellcheck disable=SC2039 48 | command [ "`echo -e test`" = '-e test' ] && __SHUNIT_CMD_ECHO_ESC='echo' 49 | 50 | __SHUNIT_UNAME_S=`uname -s` 51 | case "${__SHUNIT_UNAME_S}" in 52 | BSD) __SHUNIT_CMD_EXPR='gexpr' ;; 53 | *) __SHUNIT_CMD_EXPR='expr' ;; 54 | esac 55 | __SHUNIT_CMD_TPUT='tput' 56 | 57 | # Commands a user can override if needed. 58 | SHUNIT_CMD_EXPR=${SHUNIT_CMD_EXPR:-${__SHUNIT_CMD_EXPR}} 59 | SHUNIT_CMD_TPUT=${SHUNIT_CMD_TPUT:-${__SHUNIT_CMD_TPUT}} 60 | 61 | # Enable color output. Options are 'never', 'always', or 'auto'. 62 | SHUNIT_COLOR=${SHUNIT_COLOR:-auto} 63 | 64 | # Specific shell checks. 65 | if command [ -n "${ZSH_VERSION:-}" ]; then 66 | setopt |grep "^shwordsplit$" >/dev/null 67 | if command [ $? -ne ${SHUNIT_TRUE} ]; then 68 | _shunit_fatal 'zsh shwordsplit option is required for proper operation' 69 | fi 70 | if command [ -z "${SHUNIT_PARENT:-}" ]; then 71 | _shunit_fatal "zsh does not pass \$0 through properly. please declare \ 72 | \"SHUNIT_PARENT=\$0\" before calling shUnit2" 73 | fi 74 | fi 75 | 76 | # 77 | # Constants 78 | # 79 | 80 | __SHUNIT_MODE_SOURCED='sourced' 81 | __SHUNIT_MODE_STANDALONE='standalone' 82 | __SHUNIT_PARENT=${SHUNIT_PARENT:-$0} 83 | 84 | # User provided test prefix to display in front of the name of the test being 85 | # executed. Define by setting the SHUNIT_TEST_PREFIX variable. 86 | __SHUNIT_TEST_PREFIX=${SHUNIT_TEST_PREFIX:-} 87 | 88 | # ANSI colors. 89 | __SHUNIT_ANSI_NONE='\033[0m' 90 | __SHUNIT_ANSI_RED='\033[1;31m' 91 | __SHUNIT_ANSI_GREEN='\033[1;32m' 92 | __SHUNIT_ANSI_YELLOW='\033[1;33m' 93 | __SHUNIT_ANSI_CYAN='\033[1;36m' 94 | 95 | # Set the constants readonly. 96 | __shunit_constants=`set |grep '^__SHUNIT_' |cut -d= -f1` 97 | echo "${__shunit_constants}" |grep '^Binary file' >/dev/null && \ 98 | __shunit_constants=`set |grep -a '^__SHUNIT_' |cut -d= -f1` 99 | for __shunit_const in ${__shunit_constants}; do 100 | if command [ -z "${ZSH_VERSION:-}" ]; then 101 | readonly "${__shunit_const}" 102 | else 103 | case ${ZSH_VERSION} in 104 | [123].*) readonly "${__shunit_const}" ;; 105 | *) readonly -g "${__shunit_const}" # Declare readonly constants globally. 106 | esac 107 | fi 108 | done 109 | unset __shunit_const __shunit_constants 110 | 111 | # 112 | # Internal variables. 113 | # 114 | 115 | # Variables. 116 | __shunit_lineno='' # Line number of executed test. 117 | __shunit_mode=${__SHUNIT_MODE_SOURCED} # Operating mode. 118 | __shunit_reportGenerated=${SHUNIT_FALSE} # Is report generated. 119 | __shunit_script='' # Filename of unittest script (standalone mode). 120 | __shunit_skip=${SHUNIT_FALSE} # Is skipping enabled. 121 | __shunit_suite='' # Suite of tests to execute. 122 | __shunit_clean=${SHUNIT_FALSE} # _shunit_cleanup() was already called. 123 | 124 | # ANSI colors (populated by _shunit_configureColor()). 125 | __shunit_ansi_none='' 126 | __shunit_ansi_red='' 127 | __shunit_ansi_green='' 128 | __shunit_ansi_yellow='' 129 | __shunit_ansi_cyan='' 130 | 131 | # Counts of tests. 132 | __shunit_testSuccess=${SHUNIT_TRUE} 133 | __shunit_testsTotal=0 134 | __shunit_testsPassed=0 135 | __shunit_testsFailed=0 136 | 137 | # Counts of asserts. 138 | __shunit_assertsTotal=0 139 | __shunit_assertsPassed=0 140 | __shunit_assertsFailed=0 141 | __shunit_assertsSkipped=0 142 | 143 | # 144 | # Macros. 145 | # 146 | 147 | # shellcheck disable=SC2016,SC2089 148 | _SHUNIT_LINENO_='eval __shunit_lineno=""; if command [ "${1:-}" = "--lineno" ]; then command [ -n "$2" ] && __shunit_lineno="[$2] "; shift 2; fi' 149 | 150 | #----------------------------------------------------------------------------- 151 | # Assertion functions. 152 | # 153 | 154 | # Assert that two values are equal to one another. 155 | # 156 | # Args: 157 | # message: string: failure message [optional] 158 | # expected: string: expected value 159 | # actual: string: actual value 160 | # Returns: 161 | # integer: success (TRUE/FALSE/ERROR constant) 162 | assertEquals() { 163 | # shellcheck disable=SC2090 164 | ${_SHUNIT_LINENO_} 165 | if command [ $# -lt 2 -o $# -gt 3 ]; then 166 | _shunit_error "assertEquals() requires two or three arguments; $# given" 167 | _shunit_assertFail 168 | return ${SHUNIT_ERROR} 169 | fi 170 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 171 | 172 | shunit_message_=${__shunit_lineno} 173 | if command [ $# -eq 3 ]; then 174 | shunit_message_="${shunit_message_}$1" 175 | shift 176 | fi 177 | shunit_expected_=$1 178 | shunit_actual_=$2 179 | 180 | shunit_return=${SHUNIT_TRUE} 181 | if command [ "${shunit_expected_}" = "${shunit_actual_}" ]; then 182 | _shunit_assertPass 183 | else 184 | failNotEquals "${shunit_message_}" "${shunit_expected_}" "${shunit_actual_}" 185 | shunit_return=${SHUNIT_FALSE} 186 | fi 187 | 188 | unset shunit_message_ shunit_expected_ shunit_actual_ 189 | return ${shunit_return} 190 | } 191 | # shellcheck disable=SC2016,SC2034 192 | _ASSERT_EQUALS_='eval assertEquals --lineno "${LINENO:-}"' 193 | 194 | # Assert that two values are not equal to one another. 195 | # 196 | # Args: 197 | # message: string: failure message [optional] 198 | # expected: string: expected value 199 | # actual: string: actual value 200 | # Returns: 201 | # integer: success (TRUE/FALSE/ERROR constant) 202 | assertNotEquals() { 203 | # shellcheck disable=SC2090 204 | ${_SHUNIT_LINENO_} 205 | if command [ $# -lt 2 -o $# -gt 3 ]; then 206 | _shunit_error "assertNotEquals() requires two or three arguments; $# given" 207 | _shunit_assertFail 208 | return ${SHUNIT_ERROR} 209 | fi 210 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 211 | 212 | shunit_message_=${__shunit_lineno} 213 | if command [ $# -eq 3 ]; then 214 | shunit_message_="${shunit_message_}$1" 215 | shift 216 | fi 217 | shunit_expected_=$1 218 | shunit_actual_=$2 219 | 220 | shunit_return=${SHUNIT_TRUE} 221 | if command [ "${shunit_expected_}" != "${shunit_actual_}" ]; then 222 | _shunit_assertPass 223 | else 224 | failSame "${shunit_message_}" "${shunit_expected_}" "${shunit_actual_}" 225 | shunit_return=${SHUNIT_FALSE} 226 | fi 227 | 228 | unset shunit_message_ shunit_expected_ shunit_actual_ 229 | return ${shunit_return} 230 | } 231 | # shellcheck disable=SC2016,SC2034 232 | _ASSERT_NOT_EQUALS_='eval assertNotEquals --lineno "${LINENO:-}"' 233 | 234 | # Assert that a container contains a content. 235 | # 236 | # Args: 237 | # message: string: failure message [optional] 238 | # container: string: container to analyze 239 | # content: string: content to find 240 | # Returns: 241 | # integer: success (TRUE/FALSE/ERROR constant) 242 | assertContains() { 243 | # shellcheck disable=SC2090 244 | ${_SHUNIT_LINENO_} 245 | if command [ $# -lt 2 -o $# -gt 3 ]; then 246 | _shunit_error "assertContains() requires two or three arguments; $# given" 247 | _shunit_assertFail 248 | return ${SHUNIT_ERROR} 249 | fi 250 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 251 | 252 | shunit_message_=${__shunit_lineno} 253 | if command [ $# -eq 3 ]; then 254 | shunit_message_="${shunit_message_}$1" 255 | shift 256 | fi 257 | shunit_container_=$1 258 | shunit_content_=$2 259 | 260 | shunit_return=${SHUNIT_TRUE} 261 | if echo "$shunit_container_" | grep -F -- "$shunit_content_" > /dev/null; then 262 | _shunit_assertPass 263 | else 264 | failNotFound "${shunit_message_}" "${shunit_content_}" 265 | shunit_return=${SHUNIT_FALSE} 266 | fi 267 | 268 | unset shunit_message_ shunit_container_ shunit_content_ 269 | return ${shunit_return} 270 | } 271 | # shellcheck disable=SC2016,SC2034 272 | _ASSERT_CONTAINS_='eval assertContains --lineno "${LINENO:-}"' 273 | 274 | # Assert that a container does not contain a content. 275 | # 276 | # Args: 277 | # message: string: failure message [optional] 278 | # container: string: container to analyze 279 | # content: string: content to look for 280 | # Returns: 281 | # integer: success (TRUE/FALSE/ERROR constant) 282 | assertNotContains() { 283 | # shellcheck disable=SC2090 284 | ${_SHUNIT_LINENO_} 285 | if command [ $# -lt 2 -o $# -gt 3 ]; then 286 | _shunit_error "assertNotContains() requires two or three arguments; $# given" 287 | _shunit_assertFail 288 | return ${SHUNIT_ERROR} 289 | fi 290 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 291 | 292 | shunit_message_=${__shunit_lineno} 293 | if command [ $# -eq 3 ]; then 294 | shunit_message_="${shunit_message_}$1" 295 | shift 296 | fi 297 | shunit_container_=$1 298 | shunit_content_=$2 299 | 300 | shunit_return=${SHUNIT_TRUE} 301 | if echo "$shunit_container_" | grep -F -- "$shunit_content_" > /dev/null; then 302 | failFound "${shunit_message_}" "${shunit_content_}" 303 | shunit_return=${SHUNIT_FALSE} 304 | else 305 | _shunit_assertPass 306 | fi 307 | 308 | unset shunit_message_ shunit_container_ shunit_content_ 309 | return ${shunit_return} 310 | } 311 | # shellcheck disable=SC2016,SC2034 312 | _ASSERT_NOT_CONTAINS_='eval assertNotContains --lineno "${LINENO:-}"' 313 | 314 | # Assert that a value is null (i.e. an empty string) 315 | # 316 | # Args: 317 | # message: string: failure message [optional] 318 | # actual: string: actual value 319 | # Returns: 320 | # integer: success (TRUE/FALSE/ERROR constant) 321 | assertNull() { 322 | # shellcheck disable=SC2090 323 | ${_SHUNIT_LINENO_} 324 | if command [ $# -lt 1 -o $# -gt 2 ]; then 325 | _shunit_error "assertNull() requires one or two arguments; $# given" 326 | _shunit_assertFail 327 | return ${SHUNIT_ERROR} 328 | fi 329 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 330 | 331 | shunit_message_=${__shunit_lineno} 332 | if command [ $# -eq 2 ]; then 333 | shunit_message_="${shunit_message_}$1" 334 | shift 335 | fi 336 | assertTrue "${shunit_message_}" "[ -z '$1' ]" 337 | shunit_return=$? 338 | 339 | unset shunit_message_ 340 | return ${shunit_return} 341 | } 342 | # shellcheck disable=SC2016,SC2034 343 | _ASSERT_NULL_='eval assertNull --lineno "${LINENO:-}"' 344 | 345 | # Assert that a value is not null (i.e. a non-empty string) 346 | # 347 | # Args: 348 | # message: string: failure message [optional] 349 | # actual: string: actual value 350 | # Returns: 351 | # integer: success (TRUE/FALSE/ERROR constant) 352 | assertNotNull() { 353 | # shellcheck disable=SC2090 354 | ${_SHUNIT_LINENO_} 355 | if command [ $# -gt 2 ]; then # allowing 0 arguments as $1 might actually be null 356 | _shunit_error "assertNotNull() requires one or two arguments; $# given" 357 | _shunit_assertFail 358 | return ${SHUNIT_ERROR} 359 | fi 360 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 361 | 362 | shunit_message_=${__shunit_lineno} 363 | if command [ $# -eq 2 ]; then 364 | shunit_message_="${shunit_message_}$1" 365 | shift 366 | fi 367 | shunit_actual_=`_shunit_escapeCharactersInString "${1:-}"` 368 | test -n "${shunit_actual_}" 369 | assertTrue "${shunit_message_}" $? 370 | shunit_return=$? 371 | 372 | unset shunit_actual_ shunit_message_ 373 | return ${shunit_return} 374 | } 375 | # shellcheck disable=SC2016,SC2034 376 | _ASSERT_NOT_NULL_='eval assertNotNull --lineno "${LINENO:-}"' 377 | 378 | # Assert that two values are the same (i.e. equal to one another). 379 | # 380 | # Args: 381 | # message: string: failure message [optional] 382 | # expected: string: expected value 383 | # actual: string: actual value 384 | # Returns: 385 | # integer: success (TRUE/FALSE/ERROR constant) 386 | assertSame() { 387 | # shellcheck disable=SC2090 388 | ${_SHUNIT_LINENO_} 389 | if command [ $# -lt 2 -o $# -gt 3 ]; then 390 | _shunit_error "assertSame() requires two or three arguments; $# given" 391 | _shunit_assertFail 392 | return ${SHUNIT_ERROR} 393 | fi 394 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 395 | 396 | shunit_message_=${__shunit_lineno} 397 | if command [ $# -eq 3 ]; then 398 | shunit_message_="${shunit_message_}$1" 399 | shift 400 | fi 401 | assertEquals "${shunit_message_}" "$1" "$2" 402 | shunit_return=$? 403 | 404 | unset shunit_message_ 405 | return ${shunit_return} 406 | } 407 | # shellcheck disable=SC2016,SC2034 408 | _ASSERT_SAME_='eval assertSame --lineno "${LINENO:-}"' 409 | 410 | # Assert that two values are not the same (i.e. not equal to one another). 411 | # 412 | # Args: 413 | # message: string: failure message [optional] 414 | # expected: string: expected value 415 | # actual: string: actual value 416 | # Returns: 417 | # integer: success (TRUE/FALSE/ERROR constant) 418 | assertNotSame() { 419 | # shellcheck disable=SC2090 420 | ${_SHUNIT_LINENO_} 421 | if command [ $# -lt 2 -o $# -gt 3 ]; then 422 | _shunit_error "assertNotSame() requires two or three arguments; $# given" 423 | _shunit_assertFail 424 | return ${SHUNIT_ERROR} 425 | fi 426 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 427 | 428 | shunit_message_=${__shunit_lineno} 429 | if command [ $# -eq 3 ]; then 430 | shunit_message_="${shunit_message_:-}$1" 431 | shift 432 | fi 433 | assertNotEquals "${shunit_message_}" "$1" "$2" 434 | shunit_return=$? 435 | 436 | unset shunit_message_ 437 | return ${shunit_return} 438 | } 439 | # shellcheck disable=SC2016,SC2034 440 | _ASSERT_NOT_SAME_='eval assertNotSame --lineno "${LINENO:-}"' 441 | 442 | # Assert that a value or shell test condition is true. 443 | # 444 | # In shell, a value of 0 is true and a non-zero value is false. Any integer 445 | # value passed can thereby be tested. 446 | # 447 | # Shell supports much more complicated tests though, and a means to support 448 | # them was needed. As such, this function tests that conditions are true or 449 | # false through evaluation rather than just looking for a true or false. 450 | # 451 | # The following test will succeed: 452 | # assertTrue 0 453 | # assertTrue "[ 34 -gt 23 ]" 454 | # The following test will fail with a message: 455 | # assertTrue 123 456 | # assertTrue "test failed" "[ -r '/non/existent/file' ]" 457 | # 458 | # Args: 459 | # message: string: failure message [optional] 460 | # condition: string: integer value or shell conditional statement 461 | # Returns: 462 | # integer: success (TRUE/FALSE/ERROR constant) 463 | assertTrue() { 464 | # shellcheck disable=SC2090 465 | ${_SHUNIT_LINENO_} 466 | if command [ $# -lt 1 -o $# -gt 2 ]; then 467 | _shunit_error "assertTrue() takes one or two arguments; $# given" 468 | _shunit_assertFail 469 | return ${SHUNIT_ERROR} 470 | fi 471 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 472 | 473 | shunit_message_=${__shunit_lineno} 474 | if command [ $# -eq 2 ]; then 475 | shunit_message_="${shunit_message_}$1" 476 | shift 477 | fi 478 | shunit_condition_=$1 479 | 480 | # See if condition is an integer, i.e. a return value. 481 | shunit_match_=`expr "${shunit_condition_}" : '\([0-9]*\)'` 482 | shunit_return=${SHUNIT_TRUE} 483 | if command [ -z "${shunit_condition_}" ]; then 484 | # Null condition. 485 | shunit_return=${SHUNIT_FALSE} 486 | elif command [ -n "${shunit_match_}" -a "${shunit_condition_}" = "${shunit_match_}" ] 487 | then 488 | # Possible return value. Treating 0 as true, and non-zero as false. 489 | command [ "${shunit_condition_}" -ne 0 ] && shunit_return=${SHUNIT_FALSE} 490 | else 491 | # Hopefully... a condition. 492 | ( eval "${shunit_condition_}" ) >/dev/null 2>&1 493 | command [ $? -ne 0 ] && shunit_return=${SHUNIT_FALSE} 494 | fi 495 | 496 | # Record the test. 497 | if command [ ${shunit_return} -eq ${SHUNIT_TRUE} ]; then 498 | _shunit_assertPass 499 | else 500 | _shunit_assertFail "${shunit_message_}" 501 | fi 502 | 503 | unset shunit_message_ shunit_condition_ shunit_match_ 504 | return ${shunit_return} 505 | } 506 | # shellcheck disable=SC2016,SC2034 507 | _ASSERT_TRUE_='eval assertTrue --lineno "${LINENO:-}"' 508 | 509 | # Assert that a value or shell test condition is false. 510 | # 511 | # In shell, a value of 0 is true and a non-zero value is false. Any integer 512 | # value passed can thereby be tested. 513 | # 514 | # Shell supports much more complicated tests though, and a means to support 515 | # them was needed. As such, this function tests that conditions are true or 516 | # false through evaluation rather than just looking for a true or false. 517 | # 518 | # The following test will succeed: 519 | # assertFalse 1 520 | # assertFalse "[ 'apples' = 'oranges' ]" 521 | # The following test will fail with a message: 522 | # assertFalse 0 523 | # assertFalse "test failed" "[ 1 -eq 1 -a 2 -eq 2 ]" 524 | # 525 | # Args: 526 | # message: string: failure message [optional] 527 | # condition: string: integer value or shell conditional statement 528 | # Returns: 529 | # integer: success (TRUE/FALSE/ERROR constant) 530 | assertFalse() { 531 | # shellcheck disable=SC2090 532 | ${_SHUNIT_LINENO_} 533 | if command [ $# -lt 1 -o $# -gt 2 ]; then 534 | _shunit_error "assertFalse() requires one or two arguments; $# given" 535 | _shunit_assertFail 536 | return ${SHUNIT_ERROR} 537 | fi 538 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 539 | 540 | shunit_message_=${__shunit_lineno} 541 | if command [ $# -eq 2 ]; then 542 | shunit_message_="${shunit_message_}$1" 543 | shift 544 | fi 545 | shunit_condition_=$1 546 | 547 | # See if condition is an integer, i.e. a return value. 548 | shunit_match_=`expr "${shunit_condition_}" : '\([0-9]*\)'` 549 | shunit_return=${SHUNIT_TRUE} 550 | if command [ -z "${shunit_condition_}" ]; then 551 | # Null condition. 552 | shunit_return=${SHUNIT_FALSE} 553 | elif command [ -n "${shunit_match_}" -a "${shunit_condition_}" = "${shunit_match_}" ] 554 | then 555 | # Possible return value. Treating 0 as true, and non-zero as false. 556 | command [ "${shunit_condition_}" -eq 0 ] && shunit_return=${SHUNIT_FALSE} 557 | else 558 | # Hopefully... a condition. 559 | ( eval "${shunit_condition_}" ) >/dev/null 2>&1 560 | command [ $? -eq 0 ] && shunit_return=${SHUNIT_FALSE} 561 | fi 562 | 563 | # Record the test. 564 | if command [ "${shunit_return}" -eq "${SHUNIT_TRUE}" ]; then 565 | _shunit_assertPass 566 | else 567 | _shunit_assertFail "${shunit_message_}" 568 | fi 569 | 570 | unset shunit_message_ shunit_condition_ shunit_match_ 571 | return "${shunit_return}" 572 | } 573 | # shellcheck disable=SC2016,SC2034 574 | _ASSERT_FALSE_='eval assertFalse --lineno "${LINENO:-}"' 575 | 576 | #----------------------------------------------------------------------------- 577 | # Failure functions. 578 | # 579 | 580 | # Records a test failure. 581 | # 582 | # Args: 583 | # message: string: failure message [optional] 584 | # Returns: 585 | # integer: success (TRUE/FALSE/ERROR constant) 586 | fail() { 587 | # shellcheck disable=SC2090 588 | ${_SHUNIT_LINENO_} 589 | if command [ $# -gt 1 ]; then 590 | _shunit_error "fail() requires zero or one arguments; $# given" 591 | return ${SHUNIT_ERROR} 592 | fi 593 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 594 | 595 | shunit_message_=${__shunit_lineno} 596 | if command [ $# -eq 1 ]; then 597 | shunit_message_="${shunit_message_}$1" 598 | shift 599 | fi 600 | 601 | _shunit_assertFail "${shunit_message_}" 602 | 603 | unset shunit_message_ 604 | return ${SHUNIT_FALSE} 605 | } 606 | # shellcheck disable=SC2016,SC2034 607 | _FAIL_='eval fail --lineno "${LINENO:-}"' 608 | 609 | # Records a test failure, stating two values were not equal. 610 | # 611 | # Args: 612 | # message: string: failure message [optional] 613 | # expected: string: expected value 614 | # actual: string: actual value 615 | # Returns: 616 | # integer: success (TRUE/FALSE/ERROR constant) 617 | failNotEquals() { 618 | # shellcheck disable=SC2090 619 | ${_SHUNIT_LINENO_} 620 | if command [ $# -lt 2 -o $# -gt 3 ]; then 621 | _shunit_error "failNotEquals() requires one or two arguments; $# given" 622 | return ${SHUNIT_ERROR} 623 | fi 624 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 625 | 626 | shunit_message_=${__shunit_lineno} 627 | if command [ $# -eq 3 ]; then 628 | shunit_message_="${shunit_message_}$1" 629 | shift 630 | fi 631 | shunit_expected_=$1 632 | shunit_actual_=$2 633 | 634 | shunit_message_=${shunit_message_%% } 635 | _shunit_assertFail "${shunit_message_:+${shunit_message_} }expected:<${shunit_expected_}> but was:<${shunit_actual_}>" 636 | 637 | unset shunit_message_ shunit_expected_ shunit_actual_ 638 | return ${SHUNIT_FALSE} 639 | } 640 | # shellcheck disable=SC2016,SC2034 641 | _FAIL_NOT_EQUALS_='eval failNotEquals --lineno "${LINENO:-}"' 642 | 643 | # Records a test failure, stating a value was found. 644 | # 645 | # Args: 646 | # message: string: failure message [optional] 647 | # content: string: found value 648 | # Returns: 649 | # integer: success (TRUE/FALSE/ERROR constant) 650 | failFound() { 651 | # shellcheck disable=SC2090 652 | ${_SHUNIT_LINENO_} 653 | if command [ $# -lt 1 -o $# -gt 2 ]; then 654 | _shunit_error "failFound() requires one or two arguments; $# given" 655 | return ${SHUNIT_ERROR} 656 | fi 657 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 658 | 659 | shunit_message_=${__shunit_lineno} 660 | if command [ $# -eq 2 ]; then 661 | shunit_message_="${shunit_message_}$1" 662 | shift 663 | fi 664 | 665 | shunit_message_=${shunit_message_%% } 666 | _shunit_assertFail "${shunit_message_:+${shunit_message_} }Found" 667 | 668 | unset shunit_message_ 669 | return ${SHUNIT_FALSE} 670 | } 671 | # shellcheck disable=SC2016,SC2034 672 | _FAIL_FOUND_='eval failFound --lineno "${LINENO:-}"' 673 | 674 | # Records a test failure, stating a content was not found. 675 | # 676 | # Args: 677 | # message: string: failure message [optional] 678 | # content: string: content not found 679 | # Returns: 680 | # integer: success (TRUE/FALSE/ERROR constant) 681 | failNotFound() { 682 | # shellcheck disable=SC2090 683 | ${_SHUNIT_LINENO_} 684 | if command [ $# -lt 1 -o $# -gt 2 ]; then 685 | _shunit_error "failNotFound() requires one or two arguments; $# given" 686 | return ${SHUNIT_ERROR} 687 | fi 688 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 689 | 690 | shunit_message_=${__shunit_lineno} 691 | if command [ $# -eq 2 ]; then 692 | shunit_message_="${shunit_message_}$1" 693 | shift 694 | fi 695 | shunit_content_=$1 696 | 697 | shunit_message_=${shunit_message_%% } 698 | _shunit_assertFail "${shunit_message_:+${shunit_message_} }Not found:<${shunit_content_}>" 699 | 700 | unset shunit_message_ shunit_content_ 701 | return ${SHUNIT_FALSE} 702 | } 703 | # shellcheck disable=SC2016,SC2034 704 | _FAIL_NOT_FOUND_='eval failNotFound --lineno "${LINENO:-}"' 705 | 706 | # Records a test failure, stating two values should have been the same. 707 | # 708 | # Args: 709 | # message: string: failure message [optional] 710 | # expected: string: expected value 711 | # actual: string: actual value 712 | # Returns: 713 | # integer: success (TRUE/FALSE/ERROR constant) 714 | failSame() 715 | { 716 | # shellcheck disable=SC2090 717 | ${_SHUNIT_LINENO_} 718 | if command [ $# -lt 2 -o $# -gt 3 ]; then 719 | _shunit_error "failSame() requires two or three arguments; $# given" 720 | return ${SHUNIT_ERROR} 721 | fi 722 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 723 | 724 | shunit_message_=${__shunit_lineno} 725 | if command [ $# -eq 3 ]; then 726 | shunit_message_="${shunit_message_}$1" 727 | shift 728 | fi 729 | 730 | shunit_message_=${shunit_message_%% } 731 | _shunit_assertFail "${shunit_message_:+${shunit_message_} }expected not same" 732 | 733 | unset shunit_message_ 734 | return ${SHUNIT_FALSE} 735 | } 736 | # shellcheck disable=SC2016,SC2034 737 | _FAIL_SAME_='eval failSame --lineno "${LINENO:-}"' 738 | 739 | # Records a test failure, stating two values were not equal. 740 | # 741 | # This is functionally equivalent to calling failNotEquals(). 742 | # 743 | # Args: 744 | # message: string: failure message [optional] 745 | # expected: string: expected value 746 | # actual: string: actual value 747 | # Returns: 748 | # integer: success (TRUE/FALSE/ERROR constant) 749 | failNotSame() { 750 | # shellcheck disable=SC2090 751 | ${_SHUNIT_LINENO_} 752 | if command [ $# -lt 2 -o $# -gt 3 ]; then 753 | _shunit_error "failNotSame() requires one or two arguments; $# given" 754 | return ${SHUNIT_ERROR} 755 | fi 756 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 757 | 758 | shunit_message_=${__shunit_lineno} 759 | if command [ $# -eq 3 ]; then 760 | shunit_message_="${shunit_message_}$1" 761 | shift 762 | fi 763 | failNotEquals "${shunit_message_}" "$1" "$2" 764 | shunit_return=$? 765 | 766 | unset shunit_message_ 767 | return ${shunit_return} 768 | } 769 | # shellcheck disable=SC2016,SC2034 770 | _FAIL_NOT_SAME_='eval failNotSame --lineno "${LINENO:-}"' 771 | 772 | #----------------------------------------------------------------------------- 773 | # Skipping functions. 774 | # 775 | 776 | # Force remaining assert and fail functions to be "skipped". 777 | # 778 | # This function forces the remaining assert and fail functions to be "skipped", 779 | # i.e. they will have no effect. Each function skipped will be recorded so that 780 | # the total of asserts and fails will not be altered. 781 | # 782 | # Args: 783 | # None 784 | startSkipping() { __shunit_skip=${SHUNIT_TRUE}; } 785 | 786 | # Resume the normal recording behavior of assert and fail calls. 787 | # 788 | # Args: 789 | # None 790 | endSkipping() { __shunit_skip=${SHUNIT_FALSE}; } 791 | 792 | # Returns the state of assert and fail call skipping. 793 | # 794 | # Args: 795 | # None 796 | # Returns: 797 | # boolean: (TRUE/FALSE constant) 798 | isSkipping() { return ${__shunit_skip}; } 799 | 800 | #----------------------------------------------------------------------------- 801 | # Suite functions. 802 | # 803 | 804 | # Stub. This function should contains all unit test calls to be made. 805 | # 806 | # DEPRECATED (as of 2.1.0) 807 | # 808 | # This function can be optionally overridden by the user in their test suite. 809 | # 810 | # If this function exists, it will be called when shunit2 is sourced. If it 811 | # does not exist, shunit2 will search the parent script for all functions 812 | # beginning with the word 'test', and they will be added dynamically to the 813 | # test suite. 814 | # 815 | # This function should be overridden by the user in their unit test suite. 816 | # Note: see _shunit_mktempFunc() for actual implementation 817 | # 818 | # Args: 819 | # None 820 | #suite() { :; } # DO NOT UNCOMMENT THIS FUNCTION 821 | 822 | # Adds a function name to the list of tests schedule for execution. 823 | # 824 | # This function should only be called from within the suite() function. 825 | # 826 | # Args: 827 | # function: string: name of a function to add to current unit test suite 828 | suite_addTest() { 829 | shunit_func_=${1:-} 830 | 831 | __shunit_suite="${__shunit_suite:+${__shunit_suite} }${shunit_func_}" 832 | __shunit_testsTotal=`expr ${__shunit_testsTotal} + 1` 833 | 834 | unset shunit_func_ 835 | } 836 | 837 | # Stub. This function will be called once before any tests are run. 838 | # 839 | # Common one-time environment preparation tasks shared by all tests can be 840 | # defined here. 841 | # 842 | # This function should be overridden by the user in their unit test suite. 843 | # Note: see _shunit_mktempFunc() for actual implementation 844 | # 845 | # Args: 846 | # None 847 | #oneTimeSetUp() { :; } # DO NOT UNCOMMENT THIS FUNCTION 848 | 849 | # Stub. This function will be called once after all tests are finished. 850 | # 851 | # Common one-time environment cleanup tasks shared by all tests can be defined 852 | # here. 853 | # 854 | # This function should be overridden by the user in their unit test suite. 855 | # Note: see _shunit_mktempFunc() for actual implementation 856 | # 857 | # Args: 858 | # None 859 | #oneTimeTearDown() { :; } # DO NOT UNCOMMENT THIS FUNCTION 860 | 861 | # Stub. This function will be called before each test is run. 862 | # 863 | # Common environment preparation tasks shared by all tests can be defined here. 864 | # 865 | # This function should be overridden by the user in their unit test suite. 866 | # Note: see _shunit_mktempFunc() for actual implementation 867 | # 868 | # Args: 869 | # None 870 | #setUp() { :; } # DO NOT UNCOMMENT THIS FUNCTION 871 | 872 | # Note: see _shunit_mktempFunc() for actual implementation 873 | # Stub. This function will be called after each test is run. 874 | # 875 | # Common environment cleanup tasks shared by all tests can be defined here. 876 | # 877 | # This function should be overridden by the user in their unit test suite. 878 | # Note: see _shunit_mktempFunc() for actual implementation 879 | # 880 | # Args: 881 | # None 882 | #tearDown() { :; } # DO NOT UNCOMMENT THIS FUNCTION 883 | 884 | #------------------------------------------------------------------------------ 885 | # Internal shUnit2 functions. 886 | # 887 | 888 | # Create a temporary directory to store various run-time files in. 889 | # 890 | # This function is a cross-platform temporary directory creation tool. Not all 891 | # OSes have the `mktemp` function, so one is included here. 892 | # 893 | # Args: 894 | # None 895 | # Outputs: 896 | # string: the temporary directory that was created 897 | _shunit_mktempDir() { 898 | # Try the standard `mktemp` function. 899 | ( exec mktemp -dqt shunit.XXXXXX 2>/dev/null ) && return 900 | 901 | # The standard `mktemp` didn't work. Use our own. 902 | # shellcheck disable=SC2039 903 | if command [ -r '/dev/urandom' -a -x '/usr/bin/od' ]; then 904 | _shunit_random_=`/usr/bin/od -vAn -N4 -tx4 "${_shunit_file_}" 932 | #! /bin/sh 933 | exit ${SHUNIT_TRUE} 934 | EOF 935 | command chmod +x "${_shunit_file_}" 936 | done 937 | 938 | unset _shunit_file_ 939 | } 940 | 941 | # Final cleanup function to leave things as we found them. 942 | # 943 | # Besides removing the temporary directory, this function is in charge of the 944 | # final exit code of the unit test. The exit code is based on how the script 945 | # was ended (e.g. normal exit, or via Ctrl-C). 946 | # 947 | # Args: 948 | # name: string: name of the trap called (specified when trap defined) 949 | _shunit_cleanup() { 950 | _shunit_name_=$1 951 | 952 | case "${_shunit_name_}" in 953 | EXIT) ;; 954 | INT) _shunit_signal_=130 ;; # 2+128 955 | TERM) _shunit_signal_=143 ;; # 15+128 956 | *) 957 | _shunit_error "unrecognized trap value (${_shunit_name_})" 958 | _shunit_signal_=0 959 | ;; 960 | esac 961 | if command [ "${_shunit_name_}" != 'EXIT' ]; then 962 | _shunit_warn "trapped and now handling the (${_shunit_name_}) signal" 963 | fi 964 | 965 | # Do our work. 966 | if command [ ${__shunit_clean} -eq ${SHUNIT_FALSE} ]; then 967 | # Ensure tear downs are only called once. 968 | __shunit_clean=${SHUNIT_TRUE} 969 | 970 | tearDown 971 | command [ $? -eq ${SHUNIT_TRUE} ] \ 972 | || _shunit_warn "tearDown() returned non-zero return code." 973 | oneTimeTearDown 974 | command [ $? -eq ${SHUNIT_TRUE} ] \ 975 | || _shunit_warn "oneTimeTearDown() returned non-zero return code." 976 | 977 | command rm -fr "${__shunit_tmpDir}" 978 | fi 979 | 980 | if command [ "${_shunit_name_}" != 'EXIT' ]; then 981 | # Handle all non-EXIT signals. 982 | trap - 0 # Disable EXIT trap. 983 | exit ${_shunit_signal_} 984 | elif command [ ${__shunit_reportGenerated} -eq ${SHUNIT_FALSE} ]; then 985 | _shunit_assertFail 'unknown failure encountered running a test' 986 | _shunit_generateReport 987 | exit ${SHUNIT_ERROR} 988 | fi 989 | 990 | unset _shunit_name_ _shunit_signal_ 991 | } 992 | 993 | # configureColor based on user color preference. 994 | # 995 | # Args: 996 | # color: string: color mode (one of `always`, `auto`, or `none`). 997 | _shunit_configureColor() { 998 | _shunit_color_=${SHUNIT_FALSE} # By default, no color. 999 | case $1 in 1000 | 'always') _shunit_color_=${SHUNIT_TRUE} ;; 1001 | 'auto') 1002 | command [ "`_shunit_colors`" -ge 8 ] && _shunit_color_=${SHUNIT_TRUE} 1003 | ;; 1004 | 'none') ;; 1005 | *) _shunit_fatal "unrecognized color option '$1'" ;; 1006 | esac 1007 | 1008 | case ${_shunit_color_} in 1009 | ${SHUNIT_TRUE}) 1010 | __shunit_ansi_none=${__SHUNIT_ANSI_NONE} 1011 | __shunit_ansi_red=${__SHUNIT_ANSI_RED} 1012 | __shunit_ansi_green=${__SHUNIT_ANSI_GREEN} 1013 | __shunit_ansi_yellow=${__SHUNIT_ANSI_YELLOW} 1014 | __shunit_ansi_cyan=${__SHUNIT_ANSI_CYAN} 1015 | ;; 1016 | ${SHUNIT_FALSE}) 1017 | __shunit_ansi_none='' 1018 | __shunit_ansi_red='' 1019 | __shunit_ansi_green='' 1020 | __shunit_ansi_yellow='' 1021 | __shunit_ansi_cyan='' 1022 | ;; 1023 | esac 1024 | 1025 | unset _shunit_color_ _shunit_tput_ 1026 | } 1027 | 1028 | # colors returns the number of supported colors for the TERM. 1029 | _shunit_colors() { 1030 | _shunit_tput_=`${SHUNIT_CMD_TPUT} colors 2>/dev/null` 1031 | if command [ $? -eq 0 ]; then 1032 | echo "${_shunit_tput_}" 1033 | else 1034 | echo 16 1035 | fi 1036 | unset _shunit_tput_ 1037 | } 1038 | 1039 | # The actual running of the tests happens here. 1040 | # 1041 | # Args: 1042 | # None 1043 | _shunit_execSuite() { 1044 | for _shunit_test_ in ${__shunit_suite}; do 1045 | __shunit_testSuccess=${SHUNIT_TRUE} 1046 | 1047 | # Disable skipping. 1048 | endSkipping 1049 | 1050 | # Execute the per-test setup function. 1051 | setUp 1052 | command [ $? -eq ${SHUNIT_TRUE} ] \ 1053 | || _shunit_fatal "setup() returned non-zero return code." 1054 | 1055 | # Execute the test. 1056 | echo "${__SHUNIT_TEST_PREFIX}${_shunit_test_}" 1057 | eval "${_shunit_test_}" 1058 | if command [ $? -ne ${SHUNIT_TRUE} ]; then 1059 | _shunit_error "${_shunit_test_}() returned non-zero return code." 1060 | __shunit_testSuccess=${SHUNIT_ERROR} 1061 | _shunit_incFailedCount 1062 | fi 1063 | 1064 | # Execute the per-test tear-down function. 1065 | tearDown 1066 | command [ $? -eq ${SHUNIT_TRUE} ] \ 1067 | || _shunit_fatal "tearDown() returned non-zero return code." 1068 | 1069 | # Update stats. 1070 | if command [ ${__shunit_testSuccess} -eq ${SHUNIT_TRUE} ]; then 1071 | __shunit_testsPassed=`expr ${__shunit_testsPassed} + 1` 1072 | else 1073 | __shunit_testsFailed=`expr ${__shunit_testsFailed} + 1` 1074 | fi 1075 | done 1076 | 1077 | unset _shunit_test_ 1078 | } 1079 | 1080 | # Generates the user friendly report with appropriate OK/FAILED message. 1081 | # 1082 | # Args: 1083 | # None 1084 | # Output: 1085 | # string: the report of successful and failed tests, as well as totals. 1086 | _shunit_generateReport() { 1087 | command [ "${__shunit_reportGenerated}" -eq ${SHUNIT_TRUE} ] && return 1088 | 1089 | _shunit_ok_=${SHUNIT_TRUE} 1090 | 1091 | # If no exit code was provided, determine an appropriate one. 1092 | command [ "${__shunit_testsFailed}" -gt 0 \ 1093 | -o ${__shunit_testSuccess} -eq ${SHUNIT_FALSE} ] \ 1094 | && _shunit_ok_=${SHUNIT_FALSE} 1095 | 1096 | echo 1097 | _shunit_msg_="Ran ${__shunit_ansi_cyan}${__shunit_testsTotal}${__shunit_ansi_none}" 1098 | if command [ "${__shunit_testsTotal}" -eq 1 ]; then 1099 | ${__SHUNIT_CMD_ECHO_ESC} "${_shunit_msg_} test." 1100 | else 1101 | ${__SHUNIT_CMD_ECHO_ESC} "${_shunit_msg_} tests." 1102 | fi 1103 | 1104 | if command [ ${_shunit_ok_} -eq ${SHUNIT_TRUE} ]; then 1105 | _shunit_msg_="${__shunit_ansi_green}OK${__shunit_ansi_none}" 1106 | command [ "${__shunit_assertsSkipped}" -gt 0 ] \ 1107 | && _shunit_msg_="${_shunit_msg_} (${__shunit_ansi_yellow}skipped=${__shunit_assertsSkipped}${__shunit_ansi_none})" 1108 | else 1109 | _shunit_msg_="${__shunit_ansi_red}FAILED${__shunit_ansi_none}" 1110 | _shunit_msg_="${_shunit_msg_} (${__shunit_ansi_red}failures=${__shunit_assertsFailed}${__shunit_ansi_none}" 1111 | command [ "${__shunit_assertsSkipped}" -gt 0 ] \ 1112 | && _shunit_msg_="${_shunit_msg_},${__shunit_ansi_yellow}skipped=${__shunit_assertsSkipped}${__shunit_ansi_none}" 1113 | _shunit_msg_="${_shunit_msg_})" 1114 | fi 1115 | 1116 | echo 1117 | ${__SHUNIT_CMD_ECHO_ESC} "${_shunit_msg_}" 1118 | __shunit_reportGenerated=${SHUNIT_TRUE} 1119 | 1120 | unset _shunit_msg_ _shunit_ok_ 1121 | } 1122 | 1123 | # Test for whether a function should be skipped. 1124 | # 1125 | # Args: 1126 | # None 1127 | # Returns: 1128 | # boolean: whether the test should be skipped (TRUE/FALSE constant) 1129 | _shunit_shouldSkip() { 1130 | command [ ${__shunit_skip} -eq ${SHUNIT_FALSE} ] && return ${SHUNIT_FALSE} 1131 | _shunit_assertSkip 1132 | } 1133 | 1134 | # Records a successful test. 1135 | # 1136 | # Args: 1137 | # None 1138 | _shunit_assertPass() { 1139 | __shunit_assertsPassed=`expr ${__shunit_assertsPassed} + 1` 1140 | __shunit_assertsTotal=`expr ${__shunit_assertsTotal} + 1` 1141 | } 1142 | 1143 | # Records a test failure. 1144 | # 1145 | # Args: 1146 | # message: string: failure message to provide user 1147 | _shunit_assertFail() { 1148 | __shunit_testSuccess=${SHUNIT_FALSE} 1149 | _shunit_incFailedCount 1150 | 1151 | \[ $# -gt 0 ] && ${__SHUNIT_CMD_ECHO_ESC} \ 1152 | "${__shunit_ansi_red}ASSERT:${__shunit_ansi_none}$*" 1153 | } 1154 | 1155 | # Increment the count of failed asserts. 1156 | # 1157 | # Args: 1158 | # none 1159 | _shunit_incFailedCount() { 1160 | __shunit_assertsFailed=`expr "${__shunit_assertsFailed}" + 1` 1161 | __shunit_assertsTotal=`expr "${__shunit_assertsTotal}" + 1` 1162 | } 1163 | 1164 | 1165 | # Records a skipped test. 1166 | # 1167 | # Args: 1168 | # None 1169 | _shunit_assertSkip() { 1170 | __shunit_assertsSkipped=`expr "${__shunit_assertsSkipped}" + 1` 1171 | __shunit_assertsTotal=`expr "${__shunit_assertsTotal}" + 1` 1172 | } 1173 | 1174 | # Prepare a script filename for sourcing. 1175 | # 1176 | # Args: 1177 | # script: string: path to a script to source 1178 | # Returns: 1179 | # string: filename prefixed with ./ (if necessary) 1180 | _shunit_prepForSourcing() { 1181 | _shunit_script_=$1 1182 | case "${_shunit_script_}" in 1183 | /*|./*) echo "${_shunit_script_}" ;; 1184 | *) echo "./${_shunit_script_}" ;; 1185 | esac 1186 | unset _shunit_script_ 1187 | } 1188 | 1189 | # Escape a character in a string. 1190 | # 1191 | # Args: 1192 | # c: string: unescaped character 1193 | # s: string: to escape character in 1194 | # Returns: 1195 | # string: with escaped character(s) 1196 | _shunit_escapeCharInStr() { 1197 | command [ -n "$2" ] || return # No point in doing work on an empty string. 1198 | 1199 | # Note: using shorter variable names to prevent conflicts with 1200 | # _shunit_escapeCharactersInString(). 1201 | _shunit_c_=$1 1202 | _shunit_s_=$2 1203 | 1204 | # Escape the character. 1205 | # shellcheck disable=SC1003,SC2086 1206 | echo ''${_shunit_s_}'' |command sed 's/\'${_shunit_c_}'/\\\'${_shunit_c_}'/g' 1207 | 1208 | unset _shunit_c_ _shunit_s_ 1209 | } 1210 | 1211 | # Escape a character in a string. 1212 | # 1213 | # Args: 1214 | # str: string: to escape characters in 1215 | # Returns: 1216 | # string: with escaped character(s) 1217 | _shunit_escapeCharactersInString() { 1218 | command [ -n "$1" ] || return # No point in doing work on an empty string. 1219 | 1220 | _shunit_str_=$1 1221 | 1222 | # Note: using longer variable names to prevent conflicts with 1223 | # _shunit_escapeCharInStr(). 1224 | for _shunit_char_ in '"' '$' "'" '`'; do 1225 | _shunit_str_=`_shunit_escapeCharInStr "${_shunit_char_}" "${_shunit_str_}"` 1226 | done 1227 | 1228 | echo "${_shunit_str_}" 1229 | unset _shunit_char_ _shunit_str_ 1230 | } 1231 | 1232 | # Extract list of functions to run tests against. 1233 | # 1234 | # Args: 1235 | # script: string: name of script to extract functions from 1236 | # Returns: 1237 | # string: of function names 1238 | _shunit_extractTestFunctions() { 1239 | _shunit_script_=$1 1240 | 1241 | # Extract the lines with test function names, strip of anything besides the 1242 | # function name, and output everything on a single line. 1243 | _shunit_regex_='^\s*((function test[A-Za-z0-9_-]*)|(test[A-Za-z0-9_-]* *\(\)))' 1244 | # shellcheck disable=SC2196 1245 | egrep "${_shunit_regex_}" "${_shunit_script_}" \ 1246 | |command sed 's/^[^A-Za-z0-9_-]*//;s/^function //;s/\([A-Za-z0-9_-]*\).*/\1/g' \ 1247 | |xargs 1248 | 1249 | unset _shunit_regex_ _shunit_script_ 1250 | } 1251 | 1252 | #------------------------------------------------------------------------------ 1253 | # Main. 1254 | # 1255 | 1256 | # Determine the operating mode. 1257 | if command [ $# -eq 0 -o "${1:-}" = '--' ]; then 1258 | __shunit_script=${__SHUNIT_PARENT} 1259 | __shunit_mode=${__SHUNIT_MODE_SOURCED} 1260 | else 1261 | __shunit_script=$1 1262 | command [ -r "${__shunit_script}" ] || \ 1263 | _shunit_fatal "unable to read from ${__shunit_script}" 1264 | __shunit_mode=${__SHUNIT_MODE_STANDALONE} 1265 | fi 1266 | 1267 | # Create a temporary storage location. 1268 | __shunit_tmpDir=`_shunit_mktempDir` 1269 | 1270 | # Provide a public temporary directory for unit test scripts. 1271 | # TODO(kward): document this. 1272 | SHUNIT_TMPDIR="${__shunit_tmpDir}/tmp" 1273 | command mkdir "${SHUNIT_TMPDIR}" 1274 | 1275 | # Setup traps to clean up after ourselves. 1276 | trap '_shunit_cleanup EXIT' 0 1277 | trap '_shunit_cleanup INT' 2 1278 | trap '_shunit_cleanup TERM' 15 1279 | 1280 | # Create phantom functions to work around issues with Cygwin. 1281 | _shunit_mktempFunc 1282 | PATH="${__shunit_tmpDir}:${PATH}" 1283 | 1284 | # Make sure phantom functions are executable. This will bite if `/tmp` (or the 1285 | # current `$TMPDIR`) points to a path on a partition that was mounted with the 1286 | # 'noexec' option. The noexec command was created with `_shunit_mktempFunc()`. 1287 | noexec 2>/dev/null || _shunit_fatal \ 1288 | 'Please declare TMPDIR with path on partition with exec permission.' 1289 | 1290 | # We must manually source the tests in standalone mode. 1291 | if command [ "${__shunit_mode}" = "${__SHUNIT_MODE_STANDALONE}" ]; then 1292 | # shellcheck disable=SC1090 1293 | command . "`_shunit_prepForSourcing \"${__shunit_script}\"`" 1294 | fi 1295 | 1296 | # Configure default output coloring behavior. 1297 | _shunit_configureColor "${SHUNIT_COLOR}" 1298 | 1299 | # Execute the oneTimeSetUp function (if it exists). 1300 | oneTimeSetUp 1301 | command [ $? -eq ${SHUNIT_TRUE} ] \ 1302 | || _shunit_fatal "oneTimeSetUp() returned non-zero return code." 1303 | 1304 | # Command line selected tests or suite selected tests 1305 | if command [ "$#" -ge 2 ]; then 1306 | # Argument $1 is either the filename of tests or '--'; either way, skip it. 1307 | shift 1308 | # Remaining arguments ($2 .. $#) are assumed to be test function names. 1309 | # Interate through all remaining args in "$@" in a POSIX (likely portable) way. 1310 | # Helpful tip: https://unix.stackexchange.com/questions/314032/how-to-use-arguments-like-1-2-in-a-for-loop 1311 | for _shunit_arg_ do 1312 | suite_addTest "${_shunit_arg_}" 1313 | done 1314 | unset _shunit_arg_ 1315 | else 1316 | # Execute the suite function defined in the parent test script. 1317 | # DEPRECATED as of 2.1.0. 1318 | suite 1319 | fi 1320 | 1321 | # If no tests or suite specified, dynamically build a list of functions. 1322 | if command [ -z "${__shunit_suite}" ]; then 1323 | shunit_funcs_=`_shunit_extractTestFunctions "${__shunit_script}"` 1324 | for shunit_func_ in ${shunit_funcs_}; do 1325 | suite_addTest "${shunit_func_}" 1326 | done 1327 | fi 1328 | unset shunit_func_ shunit_funcs_ 1329 | 1330 | # Execute the suite of unit tests. 1331 | _shunit_execSuite 1332 | 1333 | # Execute the oneTimeTearDown function (if it exists). 1334 | oneTimeTearDown 1335 | command [ $? -eq ${SHUNIT_TRUE} ] \ 1336 | || _shunit_fatal "oneTimeTearDown() returned non-zero return code." 1337 | 1338 | # Generate a report summary. 1339 | _shunit_generateReport 1340 | 1341 | # That's it folks. 1342 | command [ "${__shunit_testsFailed}" -eq 0 ] 1343 | exit $? 1344 | © 2019 GitHub, Inc. 1345 | Terms 1346 | Privacy 1347 | Security 1348 | Status 1349 | Help 1350 | Contact GitHub 1351 | Pricing 1352 | API 1353 | Training 1354 | Blog 1355 | About 1356 | -------------------------------------------------------------------------------- /tests/test_runner: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # This script runs all the test files that can be found at TEST_DIRECTORY path. 4 | # Note: test files should be named *tests.sh 5 | 6 | # path of the directory that stores tests 7 | TEST_DIRECTORY="./tests" 8 | failed_test=false 9 | 10 | while IFS= read -r -d '' file 11 | do 12 | printf "Test Suite: %s \n\n" "$(basename "$file" | sed 's/.sh//g')" 13 | if ! eval "$file"; then failed_test=true; fi 14 | 15 | done < <(find $TEST_DIRECTORY -iname '*tests.sh' -print0) 16 | 17 | if [ "${failed_test}" = true ] ; then 18 | exit 1 19 | fi -------------------------------------------------------------------------------- /tests/unit_tests/scan_tests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # shellcheck disable=SC2155 3 | 4 | source ./entrypoint.sh "" "style" "" "" "--test" 5 | 6 | # scan_file tests 7 | test_scan_valid_script_with_extension(){ 8 | local expected="Scanning sample.bash" 9 | local actual=$(scan_file ./test_data/script_type/sample.bash) 10 | 11 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected" 12 | } 13 | 14 | test_scan_valid_script_without_extension(){ 15 | local expected="Scanning executable_script" 16 | local actual=$(scan_file ./test_data/test_dir/executable_script) 17 | 18 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected" 19 | } 20 | 21 | test_scan_unsupported_script(){ 22 | local not_expected1="Scanning test.zsh" 23 | local not_expected2="ShellCheck only supports sh, bash, dash, and ksh scripts. To ensure your script is scanned correctly, add a proper shebang on the first line or a shell directive on the second line." 24 | local actual=$(scan_file ./test_data/script_type/test.zsh) 25 | 26 | assertNotContains "Actual messages:$actual contains the message.\n" "$actual" "$not_expected1" 27 | assertNotContains "Actual messages:$actual contains the message.\n" "$actual" "$not_expected2" 28 | } 29 | 30 | test_scan_external_sourced_file(){ 31 | local actual=$(scan_file ./test_data/test_dir/external_sources.sh) 32 | local not_expected="SC1091: Not following" 33 | local expected="Scanning external_sources.sh" 34 | 35 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected" 36 | assertNotContains "Actual messages:$actual\n contains the unexpected message: '$not_expected'\n" "$actual" "$not_expected" 37 | } 38 | 39 | test_scan_script_with_valid_shell_directive(){ 40 | local expected="Scanning script_with_valid_shell_directive" 41 | local not_expected="ShellCheck only supports sh, bash, dash, and ksh scripts. To ensure your script is scanned correctly, add a proper shebang on the first line or a shell directive on the second line." 42 | local actual=$(scan_file ./test_data/script_type/script_with_valid_shell_directive) 43 | 44 | assertContains "Actual messages:$actual Did not contain the expected message.\n" "$actual" "$expected" 45 | assertNotContains "Actual messages:$actual\n contains the unexpected message: '$not_expected'\n" "$actual" "$not_expected" 46 | } 47 | 48 | # scan_dir tests 49 | test_scan_a_directory(){ 50 | local message1="Scanning all the shell scripts at ./test_data/script_type" 51 | local message2="Scanning sample.bash" 52 | local message3="Scanning test_script_wsh.sh" 53 | local message4="ShellCheck only supports sh, bash, dash, and ksh scripts. To ensure your script is scanned correctly, add a proper shebang on the first line or a shell directive on the second line." 54 | local actual_message=$(scan_dir ./test_data/script_type) 55 | 56 | assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$message1" 57 | assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$message2" 58 | assertContains "Actual messages:$actual_message Did not contain the expected message.\n" "$actual_message" "$message3" 59 | assertNotContains "Actual message:$actual_message contains the message.\n" "$actual_message" "$message4" 60 | } 61 | 62 | test_unscanned_files_count(){ 63 | local expected_count=4 64 | scan_dir ./test_data/script_type > /dev/null 65 | local actual_count="${#invalid_files[@]}" 66 | 67 | assertEquals "$expected_count" "$actual_count" 68 | } 69 | 70 | source ./tests/shunit2 --------------------------------------------------------------------------------