Skip to content

Commit

Permalink
Add watch mode (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
ybiquitous authored Oct 23, 2022
1 parent eac17e3 commit afbafc6
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 73 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.

## Head

- Add watch mode (`--watch`).
- Improve type coverage.

## 0.8.1
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ You can read more about Easytest on the [official website](https://ybiquitous.gi

## Usage

Here is a very easy example.
This section explains easy usage.

First, put `test/addition_test.rb` as below:

Expand Down Expand Up @@ -126,3 +126,13 @@ test "addition"
```

To-do cases will be reported as "todo".

### Watch

If you want to run tests immediately when changing code, specify the `--watch` option:

```console
$ easytest --watch
```

This *watch* mode is useful during development.
1 change: 1 addition & 0 deletions easytest.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ Gem::Specification.new do |spec|
spec.executables = ["easytest"]
spec.require_paths = ["lib"]

spec.add_dependency "listen", ">= 3.7"
spec.add_dependency "rainbow", ">= 3.1"
end
10 changes: 7 additions & 3 deletions lib/easytest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
require_relative "easytest/matcher/true"

module Easytest
def self.start
@runner = Runner.new
def self.start(no_tests_forbidden: true)
@runner = Runner.new(no_tests_forbidden: no_tests_forbidden)
end

def self.add_case(new_case)
Expand All @@ -48,7 +48,11 @@ def self.run
@runner.run
end

def self.test_dir
"test"
end

def self.test_files_location
"test/**/*_test.rb"
"#{test_dir}/**/*_test.rb"
end
end
102 changes: 75 additions & 27 deletions lib/easytest/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ class CLI
FATAL = 2

def run
exit_code = parse_options
exit_code = parse_argv
return exit_code if exit_code

if watch?
watch_files
return SUCCESS
end

Easytest.start
setup
successful = Easytest.run
Expand All @@ -21,34 +26,41 @@ def run

attr_reader :argv
attr_reader :start_time
attr_reader :options
attr_reader :parser

def initialize(argv)
@argv = argv
@start_time = Time.now
@options = {}
@parser = init_parser
end

def parse_options
# @type var options: Hash[Symbol, bool]
options = {}
def init_parser
OptionParser.new do |op|
op.program_name = "easytest"
op.version = "#{op.program_name} #{Easytest::VERSION}"

parser = OptionParser.new do |p|
p.program_name = "easytest"
p.version = "#{p.program_name} #{Easytest::VERSION}"
op.on "-w", "--watch" do
options[:watch] = true
end

p.on "--help" do
op.on "--help" do
options[:help] = true
end

p.on "--version" do
op.on "--version" do
options[:version] = true
end
end
end

def parse_argv
begin
parser.parse!(argv)

if options[:help]
puts help(parser)
puts help
return SUCCESS
end

Expand All @@ -64,37 +76,73 @@ def parse_options
end
end

def help(parser)
def help
program = Rainbow(parser.program_name).green
heading = ->(text) { Rainbow(text).bright }
secondary = ->(text) { Rainbow(text).dimgray }
option = ->(text) { Rainbow(text).yellow }
prompt = ->() { Rainbow("$").cyan }

<<~MSG
#{Rainbow("USAGE").bright}
#{parser.program_name} [options] [<file, directory, or pattern>...]
#{heading["USAGE"]}
#{program} [options] [<file, directory, or pattern>...]
#{Rainbow("OPTIONS").bright}
--help Show help
--version Show version
#{heading["OPTIONS"]}
#{option["-w, --watch"]} Watch file changes and rerun test
#{option["--help"]} Show help
#{option["--version"]} Show version
#{Rainbow("EXAMPLES").bright}
# Run all tests (test/**/*_test.rb)
$ easytest
#{heading["EXAMPLES"]}
#{secondary["# Run all tests (test/**/*_test.rb)"]}
#{prompt[]} #{program}
# Run only test files
$ easytest test/example_test.rb
#{secondary["# Run only test files"]}
#{prompt[]} #{program} test/example_test.rb
# Run only test files in specified directories
$ easytest test/example
#{secondary["# Run only test files in given directories"]}
#{prompt[]} #{program} test/example
# Run only test files that matches specified patterns
$ easytest example
#{secondary["# Run only test files that matches given patterns"]}
#{prompt[]} #{program} example
MSG
end

def setup
Dir.glob(Easytest.test_files_location)
.map { |file| File.absolute_path(file) }
test_files
.filter do |file|
argv.empty? || argv.any? { |pattern| file.include?(pattern) }
end
.each { |test_file| load test_file }
end

def test_files
Dir.glob(Easytest.test_files_location).map do |file|
File.absolute_path(file)
end
end

def watch?
options[:watch] == true
end

def watch_files
require "listen"
listener = Listen.to(Easytest.test_dir, only: /_test\.rb$/) do |modified, added|
Easytest.start(no_tests_forbidden: false)
test_files.intersection(modified + added).each do |file|
load(file)
end
Easytest.run
end
listener.start

begin
puts "Start watching \"#{Easytest.test_files_location}\" changed. Press Ctrl-C to stop."
sleep
rescue Interrupt
puts
puts "Stopped watching."
end
end
end
end
52 changes: 30 additions & 22 deletions lib/easytest/runner.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
module Easytest
class Runner
attr_reader :start_time
attr_accessor :passed_count
attr_accessor :failed_count
attr_accessor :skipped_count
attr_accessor :todo_count
attr_accessor :file_count

def initialize(start_time: Time.now)
@start_time = start_time
@passed_count = 0
@failed_count = 0
@skipped_count = 0
@todo_count = 0
@file_count = 0
end

def run
include_only_case = cases.any?(&:only?)
hooks_by_file = hooks.group_by(&:file)
Expand Down Expand Up @@ -71,12 +55,7 @@ def run
end

if no_tests?
$stderr.puts <<~MSG
#{Rainbow("Oops. No tests found!").red.bright}
#{Rainbow("Put `#{Easytest.test_files_location}` files to include at least one test case.").red}
#{Rainbow("Or specify a pattern that matches an existing test file.").red}
MSG
print_error_for_no_tests if no_tests_forbidden?
false
else
puts ""
Expand Down Expand Up @@ -104,6 +83,26 @@ def add_hook(hook)

private

attr_reader :start_time
attr_reader :no_tests_forbidden
alias no_tests_forbidden? no_tests_forbidden

attr_accessor :passed_count
attr_accessor :failed_count
attr_accessor :skipped_count
attr_accessor :todo_count
attr_accessor :file_count

def initialize(start_time: Time.now, no_tests_forbidden: true)
@start_time = start_time
@no_tests_forbidden = no_tests_forbidden
@passed_count = 0
@failed_count = 0
@skipped_count = 0
@todo_count = 0
@file_count = 0
end

def total_count
passed_count + failed_count + skipped_count + todo_count
end
Expand All @@ -116,6 +115,15 @@ def no_tests?
total_count == 0
end

def print_error_for_no_tests
$stderr.puts <<~MSG
#{Rainbow("Oops. No tests found!").red.bright}
#{Rainbow("Put `#{Easytest.test_files_location}` files to include at least one test case.").red}
#{Rainbow("Or specify a pattern that matches an existing test file.").red}
MSG
end

def print_reports(reports)
unexecuted = [:skipped, :todo]
indent = " "
Expand Down
4 changes: 3 additions & 1 deletion sig-private/easytest.rbs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
module Easytest
self.@runner: Runner

def self.start: () -> void
def self.start: (?no_tests_forbidden: bool) -> void

def self.add_case: (Case new_case) -> void

def self.add_hook: (Hook hook) -> void

def self.run: () -> void

def self.test_dir: () -> String

def self.test_files_location: () -> String
end
16 changes: 14 additions & 2 deletions sig-private/easytest/cli.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,24 @@ module Easytest

attr_reader start_time: Time

attr_reader options: Hash[Symbol, bool]

attr_reader parser: OptionParser

def initialize: (Array[String] argv) -> void

def parse_options: () -> Integer?
def init_parser: () -> OptionParser

def parse_argv: () -> Integer?

def help: (OptionParser parser) -> String
def help: () -> String

def setup: () -> void

def test_files: () -> Array[String]

def watch?: () -> bool

def watch_files: () -> void
end
end
31 changes: 18 additions & 13 deletions sig-private/easytest/runner.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,43 @@ module Easytest

@hooks: Array[Hook]

attr_reader start_time: Time
def run: () -> void

attr_accessor passed_count: Integer
def cases: () -> Array[Case]

attr_accessor failed_count: Integer
def add_case: (Case new_case) -> void

attr_accessor skipped_count: Integer
def hooks: () -> Array[Hook]

attr_accessor todo_count: Integer
def add_hook: (Hook hook) -> void

attr_accessor file_count: Integer
private

attr_reader start_time: Time

def initialize: (?start_time: Time) -> void
attr_reader no_tests_forbidden: bool
alias no_tests_forbidden? no_tests_forbidden

def run: () -> void
attr_accessor passed_count: Integer

def cases: () -> Array[Case]
attr_accessor failed_count: Integer

def add_case: (Case new_case) -> void
attr_accessor skipped_count: Integer

def hooks: () -> Array[Hook]
attr_accessor todo_count: Integer

def add_hook: (Hook hook) -> void
attr_accessor file_count: Integer

private
def initialize: (?start_time: Time, ?no_tests_forbidden: bool) -> void

def total_count: () -> Integer

def all_passed?: () -> bool

def no_tests?: () -> bool

def print_error_for_no_tests: () -> void

def print_reports: (Array[[Symbol, String]] reports) -> void

def elapsed_time: () -> Float
Expand Down
Loading

0 comments on commit afbafc6

Please sign in to comment.