feat: implement complete tree-sitter-shellspec grammar with comprehensive testing

- Add full ShellSpec grammar extending tree-sitter-bash
- Support all ShellSpec constructs: Describe, Context, It, hooks, utilities
- Include Data block parsing with statements and argument styles
- Add 61 comprehensive test cases covering real-world patterns
- Implement optimized GitHub workflows with CI/CD automation
- Configure complete development tooling (linting, formatting, pre-commit)
- Add comprehensive documentation and contribution guidelines
- Optimize grammar conflicts to zero warnings
- Support editor integration for Neovim, VS Code, Emacs

Breaking Changes:
- Initial release, no previous API to break

BREAKING CHANGE: Initial implementation of tree-sitter-shellspec grammar

# Conflicts:
#	.github/workflows/codeql.yml
#	.github/workflows/pr-lint.yml
#	.pre-commit-config.yaml

# Conflicts:
#	.github/workflows/pr-lint.yml

# Conflicts:
#	.github/workflows/pr-lint.yml
This commit is contained in:
2025-09-12 18:18:08 +03:00
parent 12d20a17b4
commit c8ba576b4e
77 changed files with 433271 additions and 242 deletions

View File

@@ -0,0 +1,131 @@
================================================================================
Basic Context block
================================================================================
Context "when condition is true"
echo "test"
End
--------------------------------------------------------------------------------
(program
(shellspec_context_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
ExampleGroup block
================================================================================
ExampleGroup "group of examples"
echo "test"
End
--------------------------------------------------------------------------------
(program
(shellspec_context_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Focused Context block
================================================================================
fContext "focused context"
echo "focused"
End
--------------------------------------------------------------------------------
(program
(shellspec_context_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Skipped Context block
================================================================================
xContext "skipped context"
echo "skipped"
End
--------------------------------------------------------------------------------
(program
(shellspec_context_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Context with raw string
================================================================================
Context 'raw string context'
local var="test"
End
--------------------------------------------------------------------------------
(program
(shellspec_context_block
description: (raw_string)
(declaration_command
(variable_assignment
name: (variable_name)
value: (string
(string_content))))))
================================================================================
Context with word description
================================================================================
Context simple_context
echo "test"
End
--------------------------------------------------------------------------------
(program
(shellspec_context_block
description: (word)
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Empty Context block
================================================================================
Context "empty context"
End
--------------------------------------------------------------------------------
(program
(shellspec_context_block
description: (string
(string_content))))

View File

@@ -0,0 +1,143 @@
================================================================================
Basic Describe block
================================================================================
Describe "basic functionality"
echo "test"
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Focused Describe block
================================================================================
fDescribe "focused test"
echo "focused"
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Skipped Describe block
================================================================================
xDescribe "skipped test"
echo "skipped"
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Describe with raw string
================================================================================
Describe 'raw string test'
echo "test"
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (raw_string)
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Describe with word description
================================================================================
Describe simple_test
echo "test"
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (word)
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Empty Describe block
================================================================================
Describe "empty test"
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (string
(string_content))))
================================================================================
Describe with multiple statements
================================================================================
Describe "multiple statements"
echo "first"
echo "second"
local var="value"
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))
(command
name: (command_name
(word))
argument: (string
(string_content)))
(declaration_command
(variable_assignment
name: (variable_name)
value: (string
(string_content))))))

219
test/corpus/hook_blocks.txt Normal file
View File

@@ -0,0 +1,219 @@
================================================================================
BeforeEach hook
================================================================================
BeforeEach
setup_environment
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(word)))))
================================================================================
BeforeEach with label
================================================================================
BeforeEach "setup database"
init_database
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(string
(string_content))))
(command
name: (command_name
(word)))))
================================================================================
AfterEach hook
================================================================================
AfterEach
cleanup_environment
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(word)))))
================================================================================
AfterEach with label
================================================================================
AfterEach "cleanup database"
cleanup_database
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(string
(string_content))))
(command
name: (command_name
(word)))))
================================================================================
BeforeAll hook
================================================================================
BeforeAll
global_setup
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(word)))))
================================================================================
BeforeAll with raw string label
================================================================================
BeforeAll 'global setup'
global_setup
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(raw_string)))
(command
name: (command_name
(word)))))
================================================================================
AfterAll hook
================================================================================
AfterAll
global_cleanup
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(word)))))
================================================================================
BeforeCall hook
================================================================================
BeforeCall
prepare_call
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(word)))))
================================================================================
AfterCall hook
================================================================================
AfterCall
verify_call
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(word)))))
================================================================================
BeforeRun hook
================================================================================
BeforeRun
prepare_run
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(word)))))
================================================================================
AfterRun hook
================================================================================
AfterRun
verify_run
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(word)))))
================================================================================
Hook with multiple statements
================================================================================
BeforeEach "complex setup"
export TEST_VAR="value"
mkdir -p /tmp/test
touch /tmp/test/file
End
--------------------------------------------------------------------------------
(program
(shellspec_hook_block
(command
name: (command_name
(string
(string_content))))
(declaration_command
(variable_assignment
name: (variable_name)
value: (string
(string_content))))
(command
name: (command_name
(word))
argument: (word)
argument: (word))
(command
name: (command_name
(word))
argument: (word))))

213
test/corpus/it_blocks.txt Normal file
View File

@@ -0,0 +1,213 @@
================================================================================
Basic It block
================================================================================
It "should work correctly"
echo "test"
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Example block
================================================================================
Example "example behavior"
echo "example"
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Specify block
================================================================================
Specify "specific behavior"
echo "specify"
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Focused It block
================================================================================
fIt "focused test"
echo "focused"
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Focused Example block
================================================================================
fExample "focused example"
echo "focused"
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Focused Specify block
================================================================================
fSpecify "focused specify"
echo "focused"
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Skipped It block
================================================================================
xIt "skipped test"
echo "skipped"
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Skipped Example block
================================================================================
xExample "skipped example"
echo "skipped"
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Skipped Specify block
================================================================================
xSpecify "skipped specify"
echo "skipped"
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
It with complex assertions
================================================================================
It "should handle complex logic"
local result
result=$(some_function "param")
[ "$result" = "expected" ]
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (string
(string_content))
(declaration_command
(variable_name))
(variable_assignment
name: (variable_name)
value: (command_substitution
(command
name: (command_name
(word))
argument: (string
(string_content)))))
(test_command
(binary_expression
left: (string
(simple_expansion
(variable_name)))
right: (string
(string_content))))))

View File

@@ -0,0 +1,236 @@
================================================================================
Describe with Context
================================================================================
Describe "main feature"
Context "when condition A"
echo "setup A"
End
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (string
(string_content))
(shellspec_context_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content))))))
================================================================================
Describe with It
================================================================================
Describe "main feature"
It "should work"
echo "test"
End
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (string
(string_content))
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content))))))
================================================================================
Context with It
================================================================================
Context "when ready"
It "should execute"
echo "executing"
End
End
--------------------------------------------------------------------------------
(program
(shellspec_context_block
description: (string
(string_content))
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))
argument: (string
(string_content))))))
================================================================================
Describe with hooks and tests
================================================================================
Describe "complete feature"
BeforeEach
setup_test
End
It "should work correctly"
run_test
End
AfterEach
cleanup_test
End
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (string
(string_content))
(shellspec_hook_block
(command
name: (command_name
(word))))
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))))
(shellspec_hook_block
(command
name: (command_name
(word))))))
================================================================================
Complex nested structure
================================================================================
Describe "main functionality"
BeforeAll
global_setup
End
Context "when user is authenticated"
BeforeEach
login_user
End
It "should access protected resource"
access_resource
End
Context "and has admin privileges"
It "should access admin panel"
access_admin
End
End
AfterEach
logout_user
End
End
AfterAll
global_cleanup
End
End
--------------------------------------------------------------------------------
(program
(shellspec_describe_block
description: (string
(string_content))
(shellspec_hook_block
(command
name: (command_name
(word))))
(shellspec_context_block
description: (string
(string_content))
(shellspec_hook_block
(command
name: (command_name
(word))))
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word))))
(shellspec_context_block
description: (string
(string_content))
(shellspec_it_block
description: (string
(string_content))
(command
name: (command_name
(word)))))
(shellspec_hook_block
(command
name: (command_name
(word)))))
(shellspec_hook_block
(command
name: (command_name
(word))))))
================================================================================
Mixed with regular bash
================================================================================
#!/bin/bash
function helper() {
echo "helper function"
}
Describe "using bash functions"
It "should call helper"
result=$(helper)
echo "$result"
End
End
--------------------------------------------------------------------------------
(program
(comment)
(function_definition
name: (word)
body: (compound_statement
(command
name: (command_name
(word))
argument: (string
(string_content)))))
(shellspec_describe_block
description: (string
(string_content))
(shellspec_it_block
description: (string
(string_content))
(variable_assignment
name: (variable_name)
value: (command_substitution
(command
name: (command_name
(word)))))
(command
name: (command_name
(word))
argument: (string
(simple_expansion
(variable_name)))))))

View File

@@ -0,0 +1,102 @@
================================================================================
Before hook statements
================================================================================
Before 'setup'
--------------------------------------------------------------------------------
(program
(shellspec_hook_statement
argument: (raw_string)))
================================================================================
After hook with multiple arguments
================================================================================
Before 'setup1' 'setup2'
--------------------------------------------------------------------------------
(program
(shellspec_hook_statement
argument: (raw_string)
argument: (raw_string)))
================================================================================
Include directive
================================================================================
Include ./lib.sh
--------------------------------------------------------------------------------
(program
(shellspec_directive_statement
path: (word)))
================================================================================
Skip with conditional
================================================================================
Skip if "function returns success" conditions
--------------------------------------------------------------------------------
(program
(shellspec_directive_statement
reason: (string
(string_content))
condition: (word)))
================================================================================
Skip with complex conditional
================================================================================
Skip if 'function returns "skip"' [ "$(conditions)" = "skip" ]
--------------------------------------------------------------------------------
(program
(ERROR
(command
name: (command_name
(raw_string))
argument: (word)
argument: (string
(command_substitution
(command
name: (command_name
(word)))))
argument: (word)
argument: (string
(string_content))
argument: (word))))
================================================================================
Top-level It without Describe
================================================================================
It 'is simple'
When call echo 'ok'
The output should eq 'ok'
End
--------------------------------------------------------------------------------
(program
(shellspec_it_block
description: (raw_string)
(command
name: (command_name
(word))
argument: (word)
argument: (word)
argument: (raw_string))
(command
name: (command_name
(word))
argument: (word)
argument: (word)
argument: (word)
argument: (raw_string))))

View File

@@ -0,0 +1,258 @@
================================================================================
Data block
================================================================================
Data
item1
item2
item3
End
--------------------------------------------------------------------------------
(program
(shellspec_data_block
statements: (command
name: (command_name
(word)))
statements: (command
name: (command_name
(word)))
statements: (command
name: (command_name
(word)))))
================================================================================
Data block with label
================================================================================
Data "test data"
"value 1"
"value 2"
End
--------------------------------------------------------------------------------
(program
(shellspec_data_block
statements: (command
name: (command_name
(string
(string_content))))
statements: (command
name: (command_name
(string
(string_content))))
statements: (command
name: (command_name
(string
(string_content))))))
================================================================================
Parameters block
================================================================================
Parameters
param1
param2
End
--------------------------------------------------------------------------------
(program
(shellspec_utility_block
(command
name: (command_name
(word)))
(command
name: (command_name
(word)))))
================================================================================
Parameters with label
================================================================================
Parameters "test parameters"
"first param"
"second param"
End
--------------------------------------------------------------------------------
(program
(shellspec_utility_block
(command
name: (command_name
(string
(string_content))))
(command
name: (command_name
(string
(string_content))))
(command
name: (command_name
(string
(string_content))))))
================================================================================
Skip block
================================================================================
Skip
echo "this is skipped"
End
--------------------------------------------------------------------------------
(program
(shellspec_utility_block
label: (word)
(command
name: (command_name
(string
(string_content))))))
================================================================================
Skip with reason
================================================================================
Skip "not implemented yet"
echo "this test"
End
--------------------------------------------------------------------------------
(program
(shellspec_utility_block
(command
name: (command_name
(string
(string_content))))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Pending block
================================================================================
Pending
echo "pending implementation"
End
--------------------------------------------------------------------------------
(program
(shellspec_utility_block
label: (word)
(command
name: (command_name
(string
(string_content))))))
================================================================================
Pending with reason
================================================================================
Pending "waiting for fix"
echo "test"
End
--------------------------------------------------------------------------------
(program
(shellspec_utility_block
(command
name: (command_name
(string
(string_content))))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Todo block
================================================================================
Todo
echo "todo item"
End
--------------------------------------------------------------------------------
(program
(shellspec_utility_block
label: (word)
(command
name: (command_name
(string
(string_content))))))
================================================================================
Todo with description
================================================================================
Todo "implement feature X"
echo "feature X"
End
--------------------------------------------------------------------------------
(program
(shellspec_utility_block
(command
name: (command_name
(string
(string_content))))
(command
name: (command_name
(word))
argument: (string
(string_content)))))
================================================================================
Empty utility block
================================================================================
Data "empty data"
End
--------------------------------------------------------------------------------
(program
(shellspec_data_block
statements: (command
name: (command_name
(string
(string_content))))))
================================================================================
Data string argument style
================================================================================
Data "inline data"
--------------------------------------------------------------------------------
(program
(shellspec_data_block
argument: (string
(string_content))))
================================================================================
Data function argument style
================================================================================
Data get_test_data
--------------------------------------------------------------------------------
(program
(shellspec_data_block
argument: (word)))

View File

@@ -0,0 +1,17 @@
#shellcheck shell=sh
It 'is simple'
When call echo 'ok'
The output should eq 'ok'
End
Describe 'lib.sh'
Include ./lib.sh # include other script
Describe 'calc()'
It 'calculates'
When call calc 1 + 1
The output should eq 2
End
End
End

View File

@@ -0,0 +1,24 @@
#shellcheck shell=sh
Describe 'this is "example group"'
Context 'this is also "example group"'
# You can write an "example" here
End
Describe '"example group" can be nestable'
# You can write an "example" here
End
Context 'this is also "example group"'
# You can write an "example" here
Describe '"example group" can be nestable'
# You can write an "example" here
End
# You can write an "example" here
End
# You can write an "example" here
End
# You can write an "example" here

View File

@@ -0,0 +1,37 @@
#shellcheck shell=sh
Describe 'example example'
It 'is "example"'
When call echo 'foo'
The output should eq 'foo'
End
Example 'is "example"'
When call echo 'bar'
The output should eq 'bar'
End
Specify 'is also "example"'
When call echo 'baz'
The output should eq 'baz'
End
Example 'this is "Not yot implemented" example block'
:
End
Todo 'what to do' # same as "Not yot implemented" example but not block
It 'not allows define "example group" in "example"'
# Describe 'example group'
# this is syntax error
# End
The value 1 should eq 1
End
End
# example group is not required
It 'is "example" without "example group"'
When call echo 'foo'
The output should eq 'foo'
End

View File

@@ -0,0 +1,75 @@
#shellcheck shell=sh disable=SC2016
Describe 'evaluation example'
Describe 'call evaluation'
It 'calls function'
foo() { echo "foo"; }
When call foo # this is evaluation
The output should eq "foo"
End
It 'calls external command also'
When call expr 1 + 2
The output should eq 3
End
It 'calls the defined function instead of external command that same name'
expr() { echo "be called"; }
When call expr 1 + 2
The output should eq "be called"
End
It 'must be one call each example'
When call echo 1
When call echo 2 # can not be called more than once.
The output should eq 1
End
It 'not calling is allowed'
The value 123 should eq 123
End
It 'can not be called after expectation'
The value 123 should eq 123
When call echo 1 # can not be called after expectation.
End
It 'calls external command'
expr() { echo "not called"; }
When run command expr 1 + 2
The output should eq 3
End
End
Describe 'run evaluation'
It 'can trap exit'
abort() { exit 1; }
When run abort # if use "call evaluation", shellspec is terminate
The status should be failure
End
It 'can not modify variable because it run with in subshell'
set_value() { SHELLSPEC_VERSION=$1; }
When run set_value 'no-version'
The value "$SHELLSPEC_VERSION" should not eq 'no-version'
End
It 'calls BeforeRun/AfterRun hook'
before_run() {
# You can temporary redefine function here
# redefined function is restored after run evaluation
# because run evaluation runs with in subshell
echo before
}
after_run() {
echo after
}
BeforeRun before_run
AfterRun after_run
When run echo 123
The line 1 of output should eq 'before'
The line 2 of output should eq 123
The line 3 of output should eq 'after'
End
End
End

View File

@@ -0,0 +1,27 @@
#shellcheck shell=sh
Describe 'expectation example'
It 'is succeeds because expectation is successful'
foo() { echo "foo"; }
When call foo
The output should eq "foo" # this is expectation
End
It 'is failure because expectation is fail'
foo() { echo "foo"; }
When call foo
The output should eq "bar"
End
Example 'you can write multiple expectations'
foo() {
echo "foo"
value=123
return 1
}
When call foo
The output should eq "foo"
The value "$value" should eq 123
The status should eq 1
End
End

View File

@@ -0,0 +1,49 @@
#shellcheck shell=sh
# Each block (example group / example) runs within subshell.
# It mean that it works like lexical scope.
Describe 'scope example'
foo() { echo "foo"; } # It can call from anywhere within this example group
# By the way, you can only use shellspec DSL or define function here.
# Of course it is possible to write freely within the defined function
# but other code may breaks isolation of tests.
It 'calls "foo"'
When call foo
The output should eq 'foo'
End
It 'defines "bar" function'
bar() { echo "bar"; }
When call bar
The output should eq 'bar'
End
It 'can not call "bar" function, because different scope'
When call bar
The status should be failure # probably status is 127
The stderr should be present # probably stderr is "bar: not found"
End
It 'redefines "foo" function'
foo() { echo "FOO"; }
When call foo
The output should eq 'FOO'
End
It 'calls "foo" function of outer scope (not previous example)'
When call foo
The output should eq 'foo'
End
Describe 'sub block'
foo() { echo "Foo"; }
It 'calls "foo" function of upper scope'
When call foo
The output should eq 'Foo'
End
End
End

View File

@@ -0,0 +1,77 @@
#shellcheck shell=sh
# shellspec has Before and After hook.
# Those hooks are execute for each example (It/Example/Specify).
# There is no hooks execute for each example group (Describe/Context).
# In other words, There is no BeforeAll / AfterAll hooks. It is design policy.
Describe 'before / after hook example'
Describe '1: before hook'
setup() { value=10; }
Before 'setup'
add_value() {
value=$((value + $1))
echo "$value"
}
It 'is called before execute example'
When call add_value 10
The output should eq 20
End
It 'is called for each example'
When call add_value 100
The output should eq 110
End
End
Describe '2: before hook'
setup1() { value1=10; }
setup2() { value2=20; }
Before 'setup1' 'setup2'
add_values() {
echo "$((value1 + value2))"
}
It 'can register multiple'
When call add_values
The output should eq 30
End
End
Describe '3: before hook'
Before 'value=10'
echo_value() { echo "$value"; }
It 'can also specify code instead of function'
When call echo_value
The output should eq 10
End
End
Describe '4: before hook'
setup() { false; } # setup fails
Before 'setup'
echo_ok() { echo ok; }
It 'is fails because before hook fails'
When call echo_ok
The output should eq 'ok'
End
# This behavior can be used to verify initialization of before hook.
End
Describe '5: after hook'
cleanup() { :; } # clean up something
Before 'cleanup'
It 'is called after execute example'
When call echo ok
The output should eq 'ok'
End
End
End

View File

@@ -0,0 +1,42 @@
#shellcheck shell=sh
hook() { %logger "$1 $2 ${SHELLSPEC_EXAMPLE_ID}"; }
Describe '1'
Before "hook before 1" "hook before 2"
After "hook after 1" "hook after 2"
Describe '1-1'
Before "hook before 3"
After "hook after 3"
# The before hook is called by defined order
%logger "==== before example 1-1-1 ===="
Example '1-1-1'
%logger "example ${SHELLSPEC_EXAMPLE_ID}"
When call :
End
%logger "==== after example 1-1-1 ===="
# The after hook is called by defined reverse order
# The before hook is called for each example
%logger "==== before example 1-1-2 ===="
Example '1-1-2'
%logger "example ${SHELLSPEC_EXAMPLE_ID}"
When call :
End
%logger "==== after example 1-1-2 ===="
# The after hook is called for each example
End
Describe '1-2'
# The before 3 hook is not called
%logger "==== before example 1-2-1 ===="
Example '1-2-1'
%logger "example ${SHELLSPEC_EXAMPLE_ID}"
When call :
End
%logger "==== after example 1-2-1 ===="
# The after 3 hook is not called
End
End

View File

@@ -0,0 +1,117 @@
#shellcheck shell=sh
Describe 'subject example'
Describe 'stdout'
foo() { echo "ok"; }
It 'uses the stdout as the subject'
When call foo
The stdout should eq "ok"
End
It 'has an alias "output"'
When call foo
The output should eq "ok"
End
Describe 'with entire'
It 'does not remove last LF'
When call foo
The entire output should eq "ok${SHELLSPEC_LF}"
End
# Without "entire", the "output" subject act as like
# the command substitution.
#
# For example "echo" outputs a newline at the end.
# In spite of that `[ "$(echo ok)" = "ok" ]` will success.
# Because the command substitution removes trailing newlines.
#
# The "entire output" subject does not remove trailing newlines.
End
End
Describe 'stderr'
foo() { echo "err" >&2; }
It 'uses the stderr as the subject'
When call foo
The stderr should eq "err"
End
It 'has an alias "error"'
When call foo
The error should eq "err"
End
Describe 'with entire'
It 'does not remove last LF'
When call foo
The entire error should eq "err${SHELLSPEC_LF}"
End
End
End
Describe 'status'
foo() { return 123; }
It 'uses the status as the subject'
When call foo
The status should eq 123
End
End
Describe 'variable'
foo() { var=456; }
It 'uses the variable as the subject'
When call foo
The variable var should eq 456
End
End
Describe 'value'
foo() { var=789; }
It 'uses the value as the subject'
When call foo
The value "$var" should eq 789
End
End
Describe 'function'
foo() { echo "ok"; }
It 'is alias for value'
The function "foo" should eq "foo"
The "foo()" should eq "foo" # shorthand for function
End
It 'uses with result modifier'
The result of "foo()" should eq "ok"
End
End
Describe 'path'
# Path helper defines path alias.
Path hosts-file='/etc/hosts'
It 'uses the resolved path as the subject'
The path hosts-file should eq '/etc/hosts'
End
It 'has an alias "file"'
Path hosts='/etc/hosts'
The file hosts should eq '/etc/hosts'
End
It 'has an alias "dir"'
Path target='/foo/bar/baz/target'
The dir target should eq '/foo/bar/baz/target'
End
It 'is same as value if path alias not found. but improve readability'
The path '/etc/hosts' should eq '/etc/hosts'
End
End
End

View File

@@ -0,0 +1,76 @@
#shellcheck shell=sh
Describe 'modifier example'
data() {
echo '1 a A'
echo '2 b B'
echo '3 c C'
echo '4 d D'
}
Describe 'line modifier'
It 'gets specified line'
When call data
The line 2 of stdout should eq "2 b B"
The stdout line 2 should eq "2 b B" # you can also write like this
End
End
Describe 'lines modifier'
It 'counts lines'
When call data
The lines of stdout should eq 4
End
End
Describe 'word modifier'
It 'gets specified word'
When call data
The word 5 of stdout should eq "b"
End
End
Describe 'length modifier'
It 'counts length'
When call data
The length of stdout should eq 23 # 6 * 4 - 1
# Each lines length is 6 including newline,
# but trailing newlines are removed.
End
End
Describe 'contents modifier'
It 'counts length'
The contents of file "data.txt" should eq "data"
End
End
Describe 'result modifier'
echo_ok() { echo ok; }
It 'calls function'
The result of function echo_ok should eq "ok"
End
End
Describe 'modifier'
It 'can use ordinal number (0 - 20)'
When call data
The second line of stdout should eq "2 b B"
End
It 'can use abbreviation of ordinal number'
When call data
The 2nd line of stdout should eq "2 b B"
End
It 'is chainable'
When call data
The word 2 of line 2 of stdout should eq "b"
End
It 'can use language chain'
When call data
The word 2 of the line 2 of the stdout should eq "b"
End
End
End

View File

@@ -0,0 +1,118 @@
#shellcheck shell=sh
Describe 'matcher example'
Describe 'status matchers'
Describe 'be success matcher'
It 'checks if status is successful'
When call true
The status should be success # status is 0
End
End
Describe 'be failure matcher'
It 'checks if status is failed'
When call false
The status should be failure # status is 1-255
End
End
# If you want to check status number, use equal matcher.
End
Describe 'stat matchers'
Describe 'exists'
It 'checks if path exists'
The path 'data.txt' should exist
End
It 'checks if path is file'
The path 'data.txt' should be file
End
It 'checks if path is directory'
The path 'data.txt' should be directory
End
End
# There are many other stat matchers.
# be empty, be symlink, be pipe, be socket, be readable, be writable,
# be executable, be block_device, be character_device,
# has setgid, has setuid
End
Describe 'variable matchers'
Before 'prepare'
Describe 'be defined'
prepare() { var=''; }
It 'checks if variable is defined'
The value "$var" should be defined
End
End
Describe 'be undefined'
prepare() { unset var; }
It 'checks if variable is undefined'
The variable var should be undefined
End
End
Describe 'be present'
prepare() { var=123; }
It 'checks if variable is present'
The value "$var" should be present # non-zero length string
End
End
Describe 'be blank'
prepare() { var=""; }
It 'checks if variable is blank'
The value "$var" should be blank # unset or zero length string
End
End
End
Describe 'string matchers'
Describe 'equal'
It 'checks if subject equals specified string'
The value "foobarbaz" should equal "foobarbaz"
End
End
Describe 'start with'
It 'checks if subject start with specified string'
The value "foobarbaz" should start with "foo"
End
End
Describe 'end with'
It 'checks if subject end with specified string'
The value "foobarbaz" should end with "baz"
End
End
Describe 'include'
It 'checks if subject include specified string'
The value "foobarbaz" should include "bar"
End
End
Describe 'match'
It 'checks if subject match specified pattern'
# Using shell script's pattern matching
The value "foobarbaz" should match pattern "f??bar*"
End
End
End
Describe 'satisfy matcher'
formula() {
eval "value=${formula:?}"
[ $(($1)) -eq 1 ]
}
It 'checks if satisfy condition'
The value 10 should satisfy formula "value >= 5"
End
End
End

100
test/spec/12.skip_spec.sh Normal file
View File

@@ -0,0 +1,100 @@
#shellcheck shell=sh
Describe 'skip example'
Describe 'calc()'
calc() { echo "$(($*))"; }
It 'can add'
When call calc 1 + 1
The output should eq 2
End
It 'can minus'
When call calc 1 - 1
The output should eq 0
End
# Skip examples of after this line in current example group
Skip "decimal point can not be calculated"
It 'can add decimal point'
When call calc 1.1 + 1.1
The output should eq 2.2
End
It 'can minus decimal point'
When call calc 1.1 - 1.1
The output should eq 0
End
Describe 'Multiplication' # example group is also skipped
It 'can multiply decimal point'
When call calc 1.1 '*' 1.1
The output should eq 1.21
End
End
End
Describe 'status_to_singnal()'
status_to_singnal() {
if [ 128 -le "$1" ] && [ "$1" -le 192 ]; then
echo "$(($1 - 128))"
else
# Not implemented: echo "status is out of range" >&2
return 1
fi
}
It 'can not convert status to singnal'
When call status_to_singnal 0
The status should be failure
# Skip expection of after this line in current example
Skip 'outputs error message is not implemented'
The error should be present
End
# This example is going to execute
It 'converts status to singnal'
When call status_to_singnal 137
The output should eq 9
End
End
# "temporary skip" can not hidden with "--skip-message quiet" option
Describe 'temporary skip'
Example 'with Skip helper'
Skip # without reason
When call foo
The status should be success
End
xExample 'with xExample (prepend "x")'
When call foo
The status should be success
End
xDescribe 'with xDescribe (prepend "x")'
Example 'this is also skipped'
When call foo
The status should be success
End
End
End
Describe 'conditional skip'
Example 'skip1'
conditions() { return 0; }
Skip if "function returns success" conditions
When call echo ok
The stdout should eq ok
End
Example 'skip2'
conditions() { echo "skip"; }
Skip if 'function returns "skip"' [ "$(conditions)" = "skip" ]
When call echo ok
The stdout should eq ok
End
End
End

View File

@@ -0,0 +1,22 @@
#shellcheck shell=sh
# Pending is better than skip in some case. Skip is just only skips,
# but Pending is runs example and decide the success or failure.
# The pend example success if the expectations fails as expected.
# The pend example fails if the expectation succeeds unexpectedly.
Describe 'pending example'
Example 'this example not fails (because it is not yet implemented as expected)'
Pending 'not yet implemented'
echo_ok() { :; } # not yet implemented
When call echo_ok
The output should eq "ok"
End
Example 'this example fails (because it is implemented as unexpected)'
Pending 'not yet implemented'
echo_ok() { echo ok; } # implemented
When call echo_ok
The output should eq "ok"
End
End

View File

@@ -0,0 +1,13 @@
#shellcheck shell=sh
Describe 'include helper example'
Describe 'include helper'
# Include helper is include external file immediately.
Include ./lib.sh
Example 'include external file'
When call calc 1 + 2
The output should eq 3
End
End
End

View File

@@ -0,0 +1,85 @@
#shellcheck shell=sh disable=SC2016
# Data helper is easy way to input data from stdin for evaluation.
# Removes `#|` from the beginning of the each line in the Data helper,
# the rest is the input data.
Describe 'Data helper'
Example 'provide with Data helper block style'
Data
#|item1 123
#|item2 456
#|item3 789
End
When call awk '{total+=$2} END{print total}'
The output should eq 1368
End
Example 'provide string with Data helper'
Data '123 + 456 + 789'
When call bc
The output should eq 1368
End
Example 'provide from function with Data helper'
data() {
echo item1 123
echo item2 456
echo item3 789
}
Data data
When call awk '{total+=$2} END{print total}'
The output should eq 1368
End
Describe 'Data helper with filter'
Example 'from block'
Data | tr 'abc' 'ABC'
#|aaa
#|bbb
End
When call cat -
The first line of output should eq 'AAA'
The second line of output should eq 'BBB'
End
Example 'from function'
foo() { printf '%s\n' "$@"; }
Data foo a b c | tr 'abc' 'ABC' # comment
When call cat -
The first line of output should eq 'A'
The second line of output should eq 'B'
The third line of output should eq "C"
The lines of entire output should eq 3
End
Example 'from string'
Data 'abc'| tr 'abc' 'ABC' # comment
When call cat -
The output should eq ABC
End
End
Describe 'variable expansion'
Before 'item=123'
Example 'not expand variable (default)'
Data:raw
#|item $item
End
When call cat -
The output should eq 'item $item'
End
Example 'expand variable'
Data:expand
#|item $item
End
When call cat -
The output should eq 'item 123'
End
# variable expansion is supported by block style only.
End
End

View File

@@ -0,0 +1,93 @@
#shellcheck shell=sh disable=SC2016
# %text directive is easy way to output text like here document.
# Removes `#|` from the beginning of the each line in the %text directive,
# the rest is the output text.
Describe '%text directive'
It 'outputs texts'
output() {
echo "start" # you can write code here
%text
#|aaa
#|bbb
#|ccc
echo "end" # you can write code here
}
When call output
The line 1 of output should eq 'start'
The line 2 of output should eq 'aaa'
The line 3 of output should eq 'bbb'
The line 4 of output should eq "ccc"
The line 5 of output should eq 'end'
End
It 'sets to variable'
output() {
texts=$(
%text
#|aaa
#|bbb
#|ccc
)
echo "$texts"
}
When call output
The line 1 of output should eq 'aaa'
The line 2 of output should eq 'bbb'
The line 3 of output should eq "ccc"
End
It 'outputs texts with filter'
output() {
%text | tr 'a-z_' 'A-Z_'
#|abc
}
When call output
The output should eq 'ABC'
End
Describe 'variable expantion'
Before 'text=abc'
Example 'not expand variable (default)'
output() {
%text:raw
#|$text
}
When call output
The output should eq '$text'
End
Example 'expand variable'
output() {
%text:expand
#|$text
}
When call output
The output should eq 'abc'
End
End
It 'outputs texts with more complex code'
output() {
if true; then
set -- 1 2 3 4 5
while [ $# -gt 0 ]; do
%text:expand | tr 'a-z_' 'A-Z_'
#|value $(($1 * 10))
shift
done
else
%text
#|text
fi
}
When call output
The line 1 of output should eq 'VALUE 10'
The line 2 of output should eq 'VALUE 20'
The line 3 of output should eq 'VALUE 30'
The line 4 of output should eq "VALUE 40"
The line 5 of output should eq 'VALUE 50'
End
End

View File

@@ -0,0 +1,34 @@
#shellcheck shell=sh
Describe '%putsn directive'
Example 'outputs arguments'
foo() { %putsn value; }
When call foo
The entire output should eq "value${SHELLSPEC_LF}"
End
End
Describe '%= directive'
Example 'is alias for %putsn'
# shellcheck disable=SC2276
foo() { %= value; }
When call foo
The entire output should eq "value${SHELLSPEC_LF}"
End
End
Describe '%puts directive'
Example 'outputs arguments without last <LF>'
foo() { %puts value; }
When call foo
The entire output should eq "value"
End
End
Describe '%- directive'
Example 'is alias for %puts'
foo() { %- value; }
When call foo
The entire output should eq "value"
End
End

View File

@@ -0,0 +1,29 @@
#shellcheck shell=sh
%const NAME: value
# shellcheck disable=SC2288
% MAJOR_VERSION: "${SHELLSPEC_VERSION%%.*}"
# % OK: "$(echo_ok)" # echo_ok not found
# %const (% is short hand) directive is define constant value.
# The characters that can be used for variable name is upper capital, number
# and underscore only. It can not be define inside of the example group or
# the example.
#
# The timing of evaluation of the value is the specfile translation process.
# So you can access shellspec variables, but you can not access variable or
# function in the specfile.
#
# This feature assumed use with conditional skip. The conditional skip may runs
# outside of the examples. As a result, sometime you may need variables defined
# outside of the examples.
Describe '%const directive'
echo_ok() { echo ok; }
version_check() { [ "$MAJOR_VERSION" -lt "$1" ]; }
Skip if 'too old version' version_check 1
Example
The variable NAME should eq 'value'
End
End

View File

@@ -0,0 +1,8 @@
#shellcheck shell=sh
Describe 'Logger helper'
%logger 'this is log'
Example 'outputs log'
%logger 'this is log'
End
End

View File

@@ -0,0 +1,18 @@
#shellcheck shell=sh
Describe 'mock stub example'
unixtime() { date +%s; }
get_next_day() { echo $(($(unixtime) + 86400)); }
Example 'redefine date command'
date() { echo 1546268400; }
When call get_next_day
The stdout should eq 1546354800
End
Example 'use the date command'
# Date is not redefined because this is another subshell
When call unixtime
The stdout should not eq 1546268400
End
End

View File

@@ -0,0 +1,36 @@
#shellcheck shell=sh
Describe 'intercept example'
Intercept begin
__begin__() {
# Define stubs for cat
cat() {
if [ "${1:-}" = "/proc/cpuinfo" ];then
%text
#|processor : 0
#|vendor_id : GenuineIntel
#|cpu family : 6
#|model : 58
#|model name : Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
#|
#|processor : 1
#|vendor_id : GenuineIntel
#|cpu family : 6
#|model : 58
#|model name : Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
#|
#|processor : 2
#|vendor_id : GenuineIntel
#|cpu family : 6
#|model : 58
#|model name : Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
else
command cat "$@"
fi
}
}
Example 'test cpunum.sh with stubbed cat /cpu/info'
When run source ./count_cpus.sh
The stdout should eq 3
End
End

View File

@@ -0,0 +1,35 @@
#shellcheck shell=sh
# Sometime, functions are defined in a single shell script.
# You will want to test it. but you do not want to run the script.
# You want to test only the function, right?
Describe 'sourced return example'
Include ./count_lines.sh
Example 'test cpunum.sh with stubbed cat /cpu/info'
Data
#|1
#|2
#|3
#|4
#|5
End
When call count_lines
The stdout should eq 5
End
Example 'test cpunum.sh with stubbed cat /cpu/info'
Data data
data() {
%putsn "line1"
%putsn "line2"
%putsn "line3"
%putsn "line4"
%puts "line5 (without newline)"
}
When call count_lines
The stdout should eq 5
End
End

View File

@@ -0,0 +1,14 @@
#shellcheck shell=sh
# regexp custom matcher is defined in "support/custom_matcher.sh" and
# imported by "spec_helper.sh"
Describe 'custom matcher'
Describe 'regexp'
number() { echo 12345; }
It 'checks with regular expression'
When call number
The output should regexp '[0-9]*$'
End
End
End

7
test/spec/spec_helper.sh Normal file
View File

@@ -0,0 +1,7 @@
#shellcheck shell=sh
# set -eu
spec_helper_configure() {
import 'support/custom_matcher'
}

View File

@@ -0,0 +1,28 @@
#shellcheck shell=sh
# imported by "spec_helper.sh"
shellspec_syntax 'shellspec_matcher_regexp'
shellspec_matcher_regexp() {
shellspec_matcher__match() {
SHELLSPEC_EXPECT="$1"
[ "${SHELLSPEC_SUBJECT+x}" ] || return 1
expr "$SHELLSPEC_SUBJECT" : "$SHELLSPEC_EXPECT" >/dev/null || return 1
return 0
}
# Message when the matcher fails with "should"
shellspec_matcher__failure_message() {
shellspec_putsn "expected: $1 match $2"
}
# Message when the matcher fails with "should not"
shellspec_matcher__failure_message_when_negated() {
shellspec_putsn "expected: $1 not match $2"
}
# checking for parameter count
shellspec_syntax_param count [ $# -eq 1 ] || return 0
shellspec_matcher_do_match "$@"
}