diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3f25db4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + ruby: ['3.1', '3.2', '3.3'] + include: + - os: ubuntu-latest + ruby: jruby + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby ${{ matrix.ruby }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + + - name: Run tests + run: bundle exec rspec diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bf62658..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: ruby -rvm: - - 1.9.3 - - jruby - - 2.0 -before_install: - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start -script: - - bundle exec rspec \ No newline at end of file diff --git a/AI_INTEGRATION_IDEAS.md b/AI_INTEGRATION_IDEAS.md new file mode 100644 index 0000000..59a3df5 --- /dev/null +++ b/AI_INTEGRATION_IDEAS.md @@ -0,0 +1,203 @@ +# AI Integration Ideas for Infinity Test + +This document outlines ideas for integrating Infinity Test with AI tools and agents, making Ruby development more efficient and intelligent. + +## 1. Claude Code Integration + +### Direct Integration +Infinity Test can work seamlessly with [Claude Code](https://claude.ai/claude-code), Anthropic's CLI tool for AI-assisted development. + +**How it works:** +- Run `infinity_test` in one terminal while using Claude Code in another +- When Claude Code makes changes to your Ruby files, Infinity Test automatically runs the relevant tests +- Immediate feedback loop: write code → tests run → see results → iterate + +**Example workflow:** +```bash +# Terminal 1: Start infinity_test +infinity_test --mode rails + +# Terminal 2: Use Claude Code +claude "Add a validation to the User model that requires email to be present" +# Infinity Test automatically runs user_spec.rb when user.rb changes +``` + +### AI-Powered Test Generation +Claude Code can generate tests based on your code changes: +```bash +claude "Write RSpec tests for the new validation I just added to User model" +# Tests are created, infinity_test runs them automatically +``` + +## 2. MCP (Model Context Protocol) Server + +Create an MCP server that exposes Infinity Test functionality to AI agents: + +```ruby +# Example MCP server endpoints +class InfinityTestMCPServer + # Get current test status + def get_test_status + { last_run: Time.now, failures: 0, pending: 2 } + end + + # Run specific tests + def run_tests(files:) + InfinityTest.run(files) + end + + # Get failed tests + def get_failures + # Return list of failed test files with error messages + end +end +``` + +**Benefits:** +- AI agents can query test results programmatically +- Agents can trigger test runs for specific files +- Agents can understand test failures and suggest fixes + +## 3. AI-Assisted Debugging + +### Failure Analysis +When tests fail, integrate with AI to: +- Analyze the failure message +- Suggest potential fixes +- Identify related code that might need changes + +```ruby +# INFINITY_TEST configuration +InfinityTest.setup do |config| + config.after(:all) do |results| + if results.failures.any? + # Send failures to AI for analysis + AIHelper.analyze_failures(results.failures) + end + end +end +``` + +### Smart Test Selection +Use AI to predict which tests are most likely to fail based on: +- Which files were changed +- Historical failure patterns +- Code complexity metrics + +## 4. Natural Language Test Commands + +Future idea: Integrate with AI to support natural language commands: + +This is just an idea: + +```bash +# Instead of +infinity_test --focus spec/models/user_spec.rb + +# Support natural language +infinity_test --ai "run tests for user authentication" +infinity_test --ai "only run the tests that failed yesterday" +infinity_test --ai "run slow tests in parallel" +``` + +## 5. Continuous Learning + +### Pattern Recognition +- Track which code changes tend to break which tests +- Learn from your project's test patterns +- Predict test failures before running + +### Smart Prioritization +- Run tests most likely to fail first +- Skip tests unlikely to be affected by changes +- Optimize test order for fastest feedback + +## 6. Integration with Popular AI Tools + +### GitHub Copilot +- Infinity Test watches files, Copilot suggests code +- Immediate test feedback on Copilot suggestions + +### Cursor IDE +- Real-time test results in Cursor's AI panel +- AI can see test output when suggesting fixes + +### Cody (Sourcegraph) +- Cody understands your test patterns +- Suggests tests based on code context + +## 7. Hooks for AI Agents + +Add hooks that AI agents can use: + +```ruby +InfinityTest.setup do |config| + # Hook for AI to process before each test run + config.before(:all) do + AIAgent.notify(:test_starting) + end + + # Hook for AI to process test results + config.after(:all) do |results| + AIAgent.process_results(results) + end + + # Custom AI-powered heuristics + config.ai_heuristics do |changed_file| + AIAgent.predict_tests_to_run(changed_file) + end +end +``` + +## 8. Test Quality Analysis + +Use AI to analyze test quality: +- Identify flaky tests +- Suggest missing test cases +- Detect duplicate tests +- Recommend test refactoring + +## 9. Documentation Generation + +After tests pass, AI can: +- Generate documentation from test descriptions +- Update README with usage examples from tests +- Create API documentation from request specs + +## 10. Future Vision: Autonomous Testing + +The ultimate goal - AI agents that: +1. Watch you code +2. Understand your intent +3. Write appropriate tests +4. Run those tests via Infinity Test +5. Suggest improvements based on results +6. Iterate until code is solid + +--- + +## Getting Started + +To integrate Infinity Test with Claude Code today: + +1. Install both tools: +```bash +gem install infinity_test +npm install -g @anthropic/claude-code +``` + +2. Start Infinity Test in watch mode: +```bash +infinity_test +``` + +3. Use Claude Code in another terminal: +```bash +claude "Help me write a new feature for my Rails app" +``` + +4. Watch the magic happen! + +--- + +*This document is part of the Infinity Test project. Contributions and ideas welcome!* diff --git a/Gemfile b/Gemfile index cd8aa9e..154ef5e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ source 'https://rubygems.org' -gemspec \ No newline at end of file +gem 'notifiers', github: 'tomas-stefano/notifiers', branch: 'main' + +gemspec diff --git a/History.markdown b/History.markdown index dff40b8..6d41915 100644 --- a/History.markdown +++ b/History.markdown @@ -1,17 +1,83 @@ -development -=========== - -- Rewrite the ENTIRE LIBRARY (Separate responsabilities!) -- The #before_env method in the configuration file was removed. -- The #before_run and #after_run method in the configuration file, was removed. Use before(:all) and after(:all) instead. -- Shared Examples to create your own strategy. -- Update all specs to RSpec 2. -- Now you can create your own observer (case you want add other gem like watchr / monitor files). -- Now you can add more patterns to monitor in a simple dsl without ugly nasty code. - -- Work Notifications using the notifiers gem. -- Work RSpec with the new way of auto discover libraries. -- Work Rubygems. \o/ +v2.0.0 +====== + +This is a major release with a complete rewrite of the library, modernization of dependencies, and many new features. + +Breaking Changes +---------------- +- Configuration file renamed from `.infinity_test` to `INFINITY_TEST` +- Removed Bacon test framework support +- Removed Growl notification support (use notifiers gem instead) +- The #before_env method in the configuration file was removed +- The #before_run and #after_run methods were removed. Use before(:all) and after(:all) instead + +New Features +------------ +- **Modern Notifications**: Integration with the notifiers gem supporting: + - osascript (macOS built-in) + - terminal_notifier (macOS) + - notify_send (Linux libnotify) + - dunstify (Linux dunst) + - auto_discover (automatic detection) + - New CLI options: `--notifications` and `--mode` for image themes + +- **Callbacks System**: Full callback support with before/after hooks + - `before(:all)` - Run before all tests + - `after(:all)` - Run after all tests + - `before(:each_ruby)` - Run before each Ruby version + - `after(:each_ruby)` - Run after each Ruby version + +- **Multi-Ruby Support**: + - RVM strategy: Run tests across multiple Ruby versions with gemset support + - RbEnv strategy: Run tests with RBENV_VERSION environment variable + - RubyDefault strategy: Run on current Ruby version + +- **Just Watch Mode**: `--just-watch` (-j) option to skip initial test run + - Useful for large applications where startup is slow + - Only watches for file changes and runs tests on change + +- **Focus Mode**: `--focus` (-f) option for running specific tests + - `--focus failures` - Run only previously failed tests + - `--focus path/to/spec.rb` - Run only specified file + +- **Framework Heuristics**: + - Rails: Watches models, controllers, helpers, mailers, jobs, lib + - Padrino: Similar to Rails with Padrino-specific paths + - Rubygems: Watches lib and test/spec directories + +- **Auto Discovery Priority**: Smart prioritization when auto-discovering + - Strategies: RVM > RbEnv > RubyDefault + - Frameworks: Rails > Padrino > Rubygems + - Test Frameworks: RSpec > Test::Unit + +- **Test Framework Improvements**: + - Complete Test::Unit/Minitest implementation with output parsing + - RSpec improvements: test_dir=, pending?, and .run? methods + +- **Modern File Watching**: + - Listen gem (default) - Event-driven, uses native OS notifications + - Filewatcher gem - Polling-based, works everywhere including VMs/NFS + +- **AI Integration Ideas**: Documentation for integrating with Claude Code and other AI tools + +Bug Fixes +--------- +- Fixed signal handler blocking issue in observer base +- Fixed ActiveSupport autoload issues +- Replaced deprecated stub with allow().to receive() in specs + +Dependencies +------------ +- Replaced watchr with listen and filewatcher observers +- Updated to modern notifiers gem (from GitHub main branch) +- Removed growl dependency + +Internal Changes +---------------- +- Rewrite of the entire library with separated responsibilities +- Shared examples for creating custom strategies, frameworks, and observers +- Updated all specs to modern RSpec syntax +- Comprehensive test coverage (250+ examples) v1.0.1 ====== diff --git a/.infinity_test b/INFINITY_TEST similarity index 86% rename from .infinity_test rename to INFINITY_TEST index 42dc0d6..fa0be2b 100644 --- a/.infinity_test +++ b/INFINITY_TEST @@ -1,8 +1,7 @@ -InfinityTest.setup do |config| - config.strategy = :ruby_default - config.test_framework = :rspec - config.notifications = :growl -end +#InfinityTest.setup do |config| +# config.strategy = :ruby_default +# config.test_framework = :rspec +#end # infinity_test do @@ -46,4 +45,4 @@ end # end # end -# end \ No newline at end of file +# end diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a7c5d8 --- /dev/null +++ b/README.md @@ -0,0 +1,627 @@ +# Infinity Test + +**To Infinity and Beyond!** + +Infinity Test is a continuous testing library and a flexible alternative to Autotest and Guard. It watches your files for changes and automatically runs your tests, providing instant feedback with desktop notifications. + +Version 2.0.0 brings a complete rewrite with modern dependencies, multi-Ruby support via RVM/RbEnv, and a powerful callbacks system. + +## Table of Contents + +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Command Line Options](#command-line-options) +- [Configuration File (INFINITY_TEST)](#configuration-file-infinity_test) +- [Ruby Version Managers](#ruby-version-managers) +- [Test Frameworks](#test-frameworks) +- [Application Frameworks](#application-frameworks) +- [Notifications](#notifications) +- [Image Themes](#image-themes) +- [Callbacks](#callbacks) +- [File Watching](#file-watching) +- [Advanced Usage](#advanced-usage) + +--- + +## Installation + +```bash +gem install infinity_test +``` + +Or add to your Gemfile: + +```ruby +gem 'infinity_test', group: :development +``` + +Then run: + +```bash +bundle install +``` + +--- + +## Quick Start + +### Step 1: Navigate to your project + +```bash +cd /path/to/your/project +``` + +### Step 2: Run Infinity Test + +```bash +infinity_test +``` + +That's it! Infinity Test will: + +1. Auto-detect your test framework (RSpec or Test::Unit) +2. Auto-detect your application framework (Rails, Padrino, or Rubygems) +3. Run all tests immediately +4. Watch for file changes and re-run relevant tests +5. Show desktop notifications with test results + +### Step 3: Start coding + +Edit your files and watch the tests run automatically! + +--- + +## Command Line Options + +| Option | Short | Description | +|--------|-------|-------------| +| `--ruby strategy` | | Ruby manager strategy: `auto_discover`, `rvm`, `rbenv`, `ruby_default` | +| `--rubies=versions` | | Ruby versions to test against (comma-separated) | +| `--test library` | | Test framework: `auto_discover`, `rspec`, `test_unit` | +| `--framework library` | | Application framework: `auto_discover`, `rails`, `rubygems`, `padrino` | +| `--options=options` | | Additional options to pass to test command | +| `--notifications library` | | Notification system: `auto_discover`, `osascript`, `terminal_notifier`, `notify_send`, `dunstify` | +| `--mode theme` | | Image theme for notifications (see [Image Themes](#image-themes)) | +| `--no-infinity-and-beyond` | `-n` | Run tests once and exit (CI mode) | +| `--just-watch` | `-j` | Skip initial test run, only watch for changes | +| `--focus [FILE]` | `-f` | Focus on specific tests or `failures` for last failed tests | +| `--no-verbose` | | Don't print commands before executing | +| `--no-bundler` | | Bypass Bundler support | +| `--help` | | Show help message | + +### Examples + +**Run with RSpec only:** +```bash +infinity_test --test rspec +``` + +**Run tests on multiple Ruby versions with RVM:** +```bash +infinity_test --ruby rvm --rubies=3.0.0,3.1.0,3.2.0 +``` + +**Run tests once and exit (for CI):** +```bash +infinity_test -n +``` + +**Skip initial test run, just watch:** +```bash +infinity_test --just-watch +``` + +**Focus on failed tests:** +```bash +infinity_test --focus failures +``` + +**Focus on a specific file:** +```bash +infinity_test --focus spec/models/user_spec.rb +``` + +**Use a specific notification theme:** +```bash +infinity_test --mode mario_bros +``` + +**Pass additional options to the test runner:** +```bash +infinity_test --options="-Ilib -Itest" +``` + +--- + +## Configuration File (INFINITY_TEST) + +You can persist your settings in an `INFINITY_TEST` file. Infinity Test looks for configuration in two locations: + +1. **Project file**: `./INFINITY_TEST` (higher priority) +2. **Global file**: `~/INFINITY_TEST` (lower priority) + +### Basic Configuration + +```ruby +InfinityTest.setup do |config| + config.test_framework = :rspec + config.framework = :rails + config.notifications = :terminal_notifier + config.mode = :mario_bros + config.verbose = true +end +``` + +### All Configuration Options + +```ruby +InfinityTest.setup do |config| + # Ruby Version Manager + # Options: :auto_discover, :rvm, :rbenv, :ruby_default + config.strategy = :auto_discover + + # Ruby versions to test against (requires RVM or RbEnv) + config.rubies = ['3.0.0', '3.1.0', '3.2.0'] + + # Gemset to use with RVM + config.gemset = 'my_project' + + # Test framework + # Options: :auto_discover, :rspec, :test_unit + config.test_framework = :rspec + + # Application framework + # Options: :auto_discover, :rails, :padrino, :rubygems + config.framework = :rails + + # File observer + # Options: :listen (event-driven), :filewatcher (polling) + config.observer = :listen + + # Notification system + # Options: :auto_discover, :osascript, :terminal_notifier, :notify_send, :dunstify + config.notifications = :auto_discover + + # Image theme for notifications + config.mode = :simpson + + # Or use custom images + config.success_image = '/path/to/success.png' + config.failure_image = '/path/to/failure.png' + config.pending_image = '/path/to/pending.png' + + # Bundler support (auto-detected if Gemfile exists) + config.bundler = true + + # Print commands before executing + config.verbose = true + + # File extension to watch + config.extension = :rb + + # Skip initial test run + config.just_watch = false + + # Run tests and exit (CI mode) + config.infinity_and_beyond = true + + # Focus mode: nil, :failures, or file path + config.focus = nil + + # Ignore specific test files + config.ignore_test_files = [ + 'spec/slow/performance_spec.rb' + ] + + # Ignore test folders + config.ignore_test_folders = [ + 'spec/integration' + ] +end +``` + +### Configuration with Callbacks + +```ruby +InfinityTest.setup do |config| + config.test_framework = :rspec + config.mode = :faces +end + +# Clear terminal before running tests +InfinityTest::Core::Base.before(:all) do + InfinityTest::Core::Base.clear_terminal +end + +# Run after all tests complete +InfinityTest::Core::Base.after(:all) do + system('say "Tests finished"') +end + +# Run before each Ruby version (when testing multiple rubies) +InfinityTest::Core::Base.before(:each_ruby) do |environment| + puts "Testing with Ruby #{environment[:ruby_version]}" +end + +# Run after each Ruby version +InfinityTest::Core::Base.after(:each_ruby) do |environment| + puts "Finished testing Ruby #{environment[:ruby_version]}" +end +``` + +--- + +## Ruby Version Managers + +Infinity Test supports testing across multiple Ruby versions using RVM or RbEnv. + +### Auto Discovery (Default) + +```ruby +config.strategy = :auto_discover +``` + +Priority order: RVM > RbEnv > RubyDefault + +### RVM Strategy + +Test against multiple Ruby versions with optional gemset support: + +```ruby +InfinityTest.setup do |config| + config.strategy = :rvm + config.rubies = ['3.0.0', '3.1.0', 'jruby-9.4.0.0'] + config.gemset = 'infinity_test' # Optional +end +``` + +**Command line:** +```bash +infinity_test --ruby rvm --rubies=3.0.0,3.1.0,jruby-9.4.0.0 +``` + +### RbEnv Strategy + +Test against multiple Ruby versions using rbenv: + +```ruby +InfinityTest.setup do |config| + config.strategy = :rbenv + config.rubies = ['3.0.0', '3.1.0', '3.2.0'] +end +``` + +**Command line:** +```bash +infinity_test --ruby rbenv --rubies=3.0.0,3.1.0 +``` + +### Ruby Default Strategy + +Use the current Ruby version only (no version manager): + +```ruby +config.strategy = :ruby_default +``` + +--- + +## Test Frameworks + +### RSpec + +Auto-detected when `spec/` directory exists with `spec_helper.rb` or `*_spec.rb` files. + +```ruby +config.test_framework = :rspec +``` + +### Test::Unit / Minitest + +Auto-detected when `test/` directory exists with `test_helper.rb` or `*_test.rb` files. + +```ruby +config.test_framework = :test_unit +``` + +--- + +## Application Frameworks + +### Rails + +Auto-detected when `config/environment.rb` exists. + +**Watched directories:** +- `app/models` → runs corresponding model specs/tests +- `app/controllers` → runs corresponding controller specs/tests +- `app/helpers` → runs corresponding helper specs/tests +- `app/mailers` → runs corresponding mailer specs/tests +- `app/jobs` → runs corresponding job specs/tests +- `lib/` → runs corresponding lib specs/tests +- `spec/` or `test/` → runs the changed spec/test file +- `spec_helper.rb` or `test_helper.rb` → runs all tests + +### Padrino + +Auto-detected when `config/boot.rb` exists with Padrino reference. + +Similar heuristics to Rails with Padrino-specific paths. + +### Rubygems (Default) + +Used for gem development and simple Ruby projects. + +**Watched directories:** +- `lib/` → runs corresponding specs/tests +- `spec/` or `test/` → runs the changed spec/test file + +--- + +## Notifications + +Desktop notifications show test results with themed images. + +### Notification Systems + +| System | Platform | Description | +|--------|----------|-------------| +| `auto_discover` | All | Automatically detect available notifier | +| `terminal_notifier` | macOS | Modern macOS notifications | +| `osascript` | macOS | Built-in AppleScript notifications | +| `notify_send` | Linux | libnotify notifications | +| `dunstify` | Linux | Dunst notification daemon | + +**Configuration:** +```ruby +config.notifications = :terminal_notifier +``` + +**Command line:** +```bash +infinity_test --notifications terminal_notifier +``` + +--- + +## Image Themes + +Infinity Test includes fun image themes for notifications. Each theme has three images: `success`, `failure`, and `pending`. + +### Available Themes + +| Theme | Description | Location | +|-------|-------------|----------| +| `simpson` | The Simpsons characters (default) | `images/simpson/` | +| `faces` | Expressive face icons | `images/faces/` | +| `fuuu` | Rage comic faces | `images/fuuu/` | +| `hands` | Hand gesture icons | `images/hands/` | +| `mario_bros` | Super Mario characters | `images/mario_bros/` | +| `rails` | Ruby on Rails themed | `images/rails/` | +| `rubies` | Ruby gemstone themed | `images/rubies/` | +| `street_fighter` | Street Fighter characters | `images/street_fighter/` | +| `toy_story` | Toy Story characters | `images/toy_story/` | + +### Using a Theme + +**Configuration file:** +```ruby +config.mode = :mario_bros +``` + +**Command line:** +```bash +infinity_test --mode mario_bros +``` + +### Custom Images + +Use your own images by setting the image paths directly: + +```ruby +InfinityTest.setup do |config| + config.success_image = '/path/to/my_success.png' + config.failure_image = '/path/to/my_failure.png' + config.pending_image = '/path/to/my_pending.png' +end +``` + +Or point to a custom directory containing `success.*`, `failure.*`, and `pending.*` images: + +```ruby +config.mode = '/path/to/my/images/directory' +``` + +--- + +## Callbacks + +Callbacks let you run custom code before or after tests. + +### Available Callbacks + +| Callback | Scope | Description | +|----------|-------|-------------| +| `before(:all)` | All | Runs once before all tests | +| `after(:all)` | All | Runs once after all tests | +| `before(:each_ruby)` | Per Ruby | Runs before testing each Ruby version | +| `after(:each_ruby)` | Per Ruby | Runs after testing each Ruby version | + +### Examples + +**Clear terminal before tests:** +```ruby +InfinityTest::Core::Base.before(:all) do + InfinityTest::Core::Base.clear_terminal +end +``` + +**Play a sound after tests:** +```ruby +InfinityTest::Core::Base.after(:all) do + system('afplay /path/to/sound.mp3') +end +``` + +**Log Ruby version being tested:** +```ruby +InfinityTest::Core::Base.before(:each_ruby) do |env| + File.write('test.log', "Testing: #{env[:ruby_version]}\n", mode: 'a') +end +``` + +**Compile extensions before each Ruby:** +```ruby +InfinityTest::Core::Base.before(:each_ruby) do |env| + system('rake compile') +end +``` + +--- + +## File Watching + +### Listen (Default) + +Event-driven file watching using native OS notifications. Fast and efficient. + +```ruby +config.observer = :listen +``` + +### Filewatcher + +Polling-based file watching. Works everywhere including VMs and NFS mounts. + +```ruby +config.observer = :filewatcher +``` + +--- + +## Advanced Usage + +### Continuous Integration Mode + +Run tests once and exit with proper exit code: + +```bash +infinity_test -n +``` + +Or in configuration: +```ruby +config.infinity_and_beyond = false +``` + +### Just Watch Mode + +Skip the initial test run and only watch for changes. Useful for large projects: + +```bash +infinity_test --just-watch +``` + +### Focus Mode + +Run only specific tests: + +```bash +# Run only previously failed tests +infinity_test --focus failures + +# Run only a specific file +infinity_test --focus spec/models/user_spec.rb +``` + +### Ignoring Files + +```ruby +InfinityTest.setup do |config| + # Ignore specific test files + config.ignore_test_files = [ + 'spec/slow/large_integration_spec.rb', + 'spec/external/api_spec.rb' + ] + + # Ignore entire directories + config.ignore_test_folders = [ + 'spec/integration', + 'spec/system' + ] +end +``` + +### Watching Non-Ruby Files + +```ruby +# Watch Python files +config.extension = :py + +# Watch JavaScript files +config.extension = :js +``` + +### Sample Complete Configuration + +```ruby +# INFINITY_TEST + +InfinityTest.setup do |config| + # Multi-Ruby testing with RVM + config.strategy = :rvm + config.rubies = ['3.1.0', '3.2.0', '3.3.0'] + config.gemset = 'myapp' + + # Test framework and app framework + config.test_framework = :rspec + config.framework = :rails + + # Notifications with Mario theme + config.notifications = :terminal_notifier + config.mode = :mario_bros + + # Performance settings + config.observer = :listen + config.just_watch = false + config.verbose = true + + # Ignore slow tests during development + config.ignore_test_folders = ['spec/system'] +end + +# Clear screen before each run +InfinityTest::Core::Base.before(:all) do + InfinityTest::Core::Base.clear_terminal +end + +# Notify via system sound when done +InfinityTest::Core::Base.after(:all) do + system('afplay /System/Library/Sounds/Glass.aiff') +end +``` + +--- + +## Contributing + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Run the tests (`infinity_test`) +4. Commit your changes (`git commit -am 'Add amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Create a Pull Request + +--- + +## License + +MIT License - see [LICENSE.txt](LICENSE.txt) for details. + +--- + +## Author + +Tomas D'Stefano + +**Happy Testing!** diff --git a/Readme.markdown b/Readme.markdown deleted file mode 100644 index 886b830..0000000 --- a/Readme.markdown +++ /dev/null @@ -1,21 +0,0 @@ -# Infinity Test - -Infinity Test is a continuous testing library and a flexible alternative to -Autotest, using the awesome Watchr library with RSpec, Test::Unit, Bacon and -with RVM functionality, giving the possibility to test with all Ruby versions -that you have in your RVM configuration. - -## To Infinity and Beyond! - -
- Infinity Test -

Photo taken from this site

-
- -## Install - - gem install infinity_test - -## Preparing the 2.0.0.rc1 - -**After LONG YEARS without developing this gem, in 2015 I'm working to release the 2.0.0 soon. Coming soon!! \o/** diff --git a/TODO.markdown b/TODO.markdown index 8def40f..233788f 100644 --- a/TODO.markdown +++ b/TODO.markdown @@ -1,35 +1,29 @@ -## Road to 2.0 - -* Test::Unit/Minitest. -* RubyDefault strategy. -* Make callbacks work in the new structure. -* Work with test pattern on Configuration overwriting the Test framework instance. -* Be possible to rewrite the rules in more nicer way, using the Hike gem to find files and paths. -* Work with gemsets. -* Rails. -* Padrino. -* Rvm. -* Test with spork and zeus and other gems spork like (Experimental). - -### 2.0.0 Bugs - -* Improve auto discover feature priorization subclasses for #run? method. -* Observer process signal must work in Ruby 2.0.0. - -### Flexibility - -* Ignore test files and test folders when run command and change files/dir. -* Verbose way of InfinityTest. -* Add specific options to the command. -* Create a infinity test generator with thor! -* Don't run integration tests. Ignore them when changed. - -### 2.0.2 - -* RbEnv (experimented feature). -* Bacon. -* Focus feature(fails, pass one file, run entire suite) with --focus (experimented feature)! -* Cucumber -* - -Maybe we could create a infinity-test-contrib repositories with other heuristics platforms +## Todo + +Infinity test is a gem that watch files changes and run tests. + +That also can run with different ruby versions all test suite or only that file change (using rvm or +rbenv), ruby default just run the test on the current ruby version. + +## What Needs to Be Done + +* ~~Make notifications work with Notifiers gem (remove growl and use the autodiscover from notifiers gem).~~ DONE +* ~~Change .infinity_test to INFINITY_TEST~~ DONE +* ~~Make Test::Unit to work (the infinity test to check test folder).~~ DONE +* ~~Make RSpec work~~ DONE +* ~~Finish RubyDefault strategy~~ DONE +* ~~Finish RVM (running different versions - the user need to specify)~~ DONE +* ~~Finish RbEnv (running different versions - the user need to specify)~~ DONE +* Make callbacks work in the new structure (loading the infinity test file). +* ~~Finish Gem autodiscover and its changes heuristics.~~ DONE +* ~~Finish Rails autodiscover and its changes heuristics.~~ DONE +* ~~Padrino autodiscover and its changes heuristics.~~ DONE +* ~~Improve auto discover feature priorization subclasses for #run? method.~~ DONE +* ~~Focus feature(fails, pass one file, run entire suite) with --focus (experimented feature)!~~ DONE +* ~~Add post-run hooks to be added to the INFINITY_TEST file that run other things (coverage, code +analysis, etc - see ideas)~~ DONE (via callbacks system) + +* ~~Give some ideas (write to a md file the ideas) about how to integrate the infinity test with AI tools/AI agents or +even Claude code ... so ruby developers can see~~ DONE (see AI_INTEGRATION_IDEAS.md) + +* ~~Update HISTORY with all changes since last version.~~ DONE diff --git a/infinity_test.gemspec b/infinity_test.gemspec index 92ec8d2..2af84ae 100644 --- a/infinity_test.gemspec +++ b/infinity_test.gemspec @@ -18,14 +18,15 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] - spec.add_dependency 'activesupport', '>= 3.2.0' - spec.add_dependency 'watchr' - spec.add_dependency 'hike', '~> 1.2' - spec.add_dependency 'notifiers', '>= 1.2.2' + spec.add_dependency 'activesupport' + spec.add_dependency 'listen' + spec.add_dependency 'filewatcher' + spec.add_dependency 'hike' + spec.add_dependency 'notifiers' - spec.add_development_dependency 'bundler', '~> 1.3' + spec.add_development_dependency 'bundler' spec.add_development_dependency 'rake' - spec.add_development_dependency 'rspec', '>= 2.14' + spec.add_development_dependency 'rspec' spec.add_development_dependency 'simplecov' spec.add_development_dependency 'pry' diff --git a/lib/infinity_test.rb b/lib/infinity_test.rb index 99b581e..d8b7e4f 100644 --- a/lib/infinity_test.rb +++ b/lib/infinity_test.rb @@ -1,4 +1,5 @@ require 'optparse' +require 'active_support' require 'active_support/core_ext' require 'active_support/deprecation' require 'hike' @@ -8,6 +9,7 @@ module InfinityTest module Core autoload :AutoDiscover, 'infinity_test/core/auto_discover' autoload :Base, 'infinity_test/core/base' + autoload :Callback, 'infinity_test/core/callback' autoload :CommandBuilder, 'infinity_test/core/command_builder' autoload :CommandRunner, 'infinity_test/core/command_runner' autoload :ConfigurationMerge, 'infinity_test/core/configuration_merge' @@ -33,8 +35,8 @@ module Framework module Observer autoload :Base, 'infinity_test/observer/base' - autoload :Watchr, 'infinity_test/observer/watchr' - autoload :EventMachine, 'infinity_test/observer/event_machine' + autoload :Listen, 'infinity_test/observer/listen' + autoload :Filewatcher, 'infinity_test/observer/filewatcher' autoload :SharedExample, 'infinity_test/observer/shared_example' end @@ -65,4 +67,3 @@ def self.setup(&block) require 'infinity_test/framework/rubygems' require 'infinity_test/test_framework/test_unit' require 'infinity_test/test_framework/rspec' -require 'infinity_test/test_framework/bacon' diff --git a/lib/infinity_test/core/auto_discover.rb b/lib/infinity_test/core/auto_discover.rb index ed1be10..63e396b 100644 --- a/lib/infinity_test/core/auto_discover.rb +++ b/lib/infinity_test/core/auto_discover.rb @@ -3,6 +3,14 @@ module Core class AutoDiscover attr_reader :base + # Priority order for auto discovery (higher priority first) + # More specific libraries should be checked before generic ones + PRIORITY = { + strategy: [:rvm, :rbenv, :ruby_default], + framework: [:rails, :padrino, :rubygems], + test_framework: [:rspec, :test_unit] + }.freeze + def initialize(base) @base = base end @@ -29,7 +37,16 @@ def discover_test_framework def auto_discover(library_type) library_base_class = "InfinityTest::#{library_type.to_s.camelize}::Base".constantize - library = library_base_class.subclasses.find { |subclass| subclass.run? } + subclasses = library_base_class.subclasses + priority_order = PRIORITY[library_type] || [] + + # Sort subclasses by priority (known priorities first, unknown last) + sorted_subclasses = subclasses.sort_by do |subclass| + name = subclass.name.demodulize.underscore.to_sym + priority_order.index(name) || priority_order.length + end + + library = sorted_subclasses.find { |subclass| subclass.run? } if library.present? library.name.demodulize.underscore.to_sym @@ -39,7 +56,7 @@ def auto_discover(library_type) The InfinityTest::Core::AutoDiscover doesn't discover nothing to run. Are you using a #{library_type} that Infinity test knows? - Infinity Test #{library_type.to_s.pluralize}: #{library_base_class.subclasses.each { |klass| klass }} + Infinity Test #{library_type.to_s.pluralize}: #{library_base_class.subclasses.map(&:name).join(', ')} } raise Exception, message diff --git a/lib/infinity_test/core/base.rb b/lib/infinity_test/core/base.rb index 7ccf6c0..8719a5d 100644 --- a/lib/infinity_test/core/base.rb +++ b/lib/infinity_test/core/base.rb @@ -23,14 +23,13 @@ class Base cattr_accessor :specific_options self.specific_options = '' - # Test Framework to use Rspec, Bacon, Test::Unit or AutoDiscover(defaults) + # Test Framework to use RSpec, Test::Unit or AutoDiscover(defaults) # ==== Options # * :rspec - # * :bacon # * :test_unit (Test unit here apply to this two libs: test/unit and minitest) - # * :auto_discover(defaults) + # * :auto_discover (default) # - # This will load a exactly a class constantize by name. + # This will load a class constantized by name. # cattr_accessor :test_framework self.test_framework = :auto_discover @@ -47,13 +46,14 @@ class Base cattr_accessor :framework self.framework = :auto_discover - # Framework to observer watch the dirs. + # Framework to observe and watch the dirs for file changes. # # ==== Options - # * watchr + # * :listen (default) - Event-driven, uses native OS notifications + # * :filewatcher - Polling-based, works everywhere including VMs/NFS # cattr_accessor :observer - self.observer = :watchr + self.observer = :listen # Ignore test files. # @@ -80,11 +80,11 @@ class Base # Set the notification framework to use with Infinity Test. # # ==== Options - # * :growl - # * :lib_notify - # * :auto_discover(defaults) - # - # This will load a exactly a class constantize by name. + # * :auto_discover (default) - Automatically detect available notifier + # * :terminal_notifier - macOS terminal-notifier + # * :osascript - macOS built-in notifications + # * :notify_send - Linux/BSD libnotify + # * :dunstify - Linux/BSD dunst # cattr_writer :notifications self.notifications = :auto_discover @@ -140,6 +140,22 @@ class Base cattr_accessor :infinity_and_beyond self.infinity_and_beyond = true + # Skip running tests on startup, only watch for file changes. + # Useful for large applications where you want to start watching quickly. + # + cattr_accessor :just_watch + self.just_watch = false + + # Focus mode for running specific tests. + # + # ==== Options + # * nil - Run all tests (default) + # * :failures - Run only previously failed tests + # * String - Run only the specified file/pattern + # + cattr_accessor :focus + self.focus = nil + # The extension files that Infinity Test will search. # You can observe python, erlang, etc files. # @@ -202,8 +218,10 @@ def self.verbose? # # ... # end # - def self.before(scope, &block) - # setting_callback(Callbacks::BeforeCallback, scope, &block) + def self.before(scope = :all, &block) + callback = Callback.new(:before, scope, &block) + callbacks.push(callback) + callback end # Callback method to handle after all run and for each ruby too! @@ -222,8 +240,32 @@ def self.before(scope, &block) # # ... # end # - def self.after(scope, &block) - # setting_callback(Callbacks::AfterCallback, scope, &block) + def self.after(scope = :all, &block) + callback = Callback.new(:after, scope, &block) + callbacks.push(callback) + callback + end + + # Run all before callbacks for the given scope + # + def self.run_before_callbacks(scope = :all, environment = nil) + callbacks.select { |c| c.before? && c.scope == scope }.each do |callback| + callback.call(environment) + end + end + + # Run all after callbacks for the given scope + # + def self.run_after_callbacks(scope = :all, environment = nil) + callbacks.select { |c| c.after? && c.scope == scope }.each do |callback| + callback.call(environment) + end + end + + # Clear all registered callbacks + # + def self.clear_callbacks! + self.callbacks = [] end # Clear the terminal (Useful in the before callback) @@ -244,10 +286,10 @@ def self.notifications(notification_name = nil, &block) .notifications is DEPRECATED. Use this instead: InfinityTest.setup do |config| - config.notifications = :growl + config.notifications = :dunstify end MESSAGE - ActiveSupport::Deprecation.warn(message) + ActiveSupport::Deprecation.new.warn(message) self.notifications = notification_name self.instance_eval(&block) if block_given? end @@ -270,7 +312,7 @@ def self.show_images(options) config.mode = 'infinity_test_dir_that_contain_images' end MESSAGE - ActiveSupport::Deprecation.warn(message) + ActiveSupport::Deprecation.new.warn(message) self.success_image = options[:success_image] || options[:sucess_image] if options[:success_image].present? || options[:sucess_image].present? # for fail typo in earlier versions. self.pending_image = options[:pending_image] if options[:pending_image].present? self.failure_image = options[:failure_image] if options[:failure_image].present? @@ -298,7 +340,7 @@ def self.use(options) config.gemset = :some_gemset end MESSAGE - ActiveSupport::Deprecation.warn(message) + ActiveSupport::Deprecation.new.warn(message) self.rubies = options[:rubies] if options[:rubies].present? self.specific_options = options[:specific_options] if options[:specific_options].present? self.test_framework = options[:test_framework] if options[:test_framework].present? @@ -311,7 +353,7 @@ def self.use(options) # def self.clear(option) message = '.clear(:terminal) is DEPRECATED. Please use .clear_terminal instead.' - ActiveSupport::Deprecation.warn(message) + ActiveSupport::Deprecation.new.warn(message) clear_terminal end @@ -322,14 +364,6 @@ def self.heuristics(&block) def self.replace_patterns(&block) # There is a spec pending. end - - private - - # def self.setting_callback(callback_class, scope, &block) - # callback_instance = callback_class.new(scope, &block) - # self.callbacks.push(callback_instance) - # callback_instance - # end end end -end \ No newline at end of file +end diff --git a/lib/infinity_test/core/callback.rb b/lib/infinity_test/core/callback.rb new file mode 100644 index 0000000..a0a4faa --- /dev/null +++ b/lib/infinity_test/core/callback.rb @@ -0,0 +1,59 @@ +module InfinityTest + module Core + class Callback + attr_reader :scope, :block, :type + + VALID_SCOPES = [:all, :each_ruby].freeze + + # Create a new callback + # + # @param type [Symbol] :before or :after + # @param scope [Symbol] :all or :each_ruby (defaults to :all) + # @param block [Proc] The block to execute + # + def initialize(type, scope = :all, &block) + @type = type + @scope = scope || :all + @block = block + + validate_scope! + end + + # Execute the callback block + # + # @param environment [Hash] Optional environment info passed to the block + # + def call(environment = nil) + if block.arity > 0 + block.call(environment) + else + block.call + end + end + + def before? + type == :before + end + + def after? + type == :after + end + + def all? + scope == :all + end + + def each_ruby? + scope == :each_ruby + end + + private + + def validate_scope! + unless VALID_SCOPES.include?(scope) + raise ArgumentError, "Invalid callback scope: #{scope}. Valid scopes are: #{VALID_SCOPES.join(', ')}" + end + end + end + end +end diff --git a/lib/infinity_test/core/configuration_merge.rb b/lib/infinity_test/core/configuration_merge.rb index 1217224..6ca247b 100644 --- a/lib/infinity_test/core/configuration_merge.rb +++ b/lib/infinity_test/core/configuration_merge.rb @@ -5,6 +5,7 @@ class ConfigurationMerge delegate :strategy, :rubies, :test_framework, :framework, :to => :options delegate :specific_options, :infinity_and_beyond, :verbose, :bundler, :to => :options + delegate :notifications, :mode, :just_watch, :focus, :to => :options def initialize(base, options) @base = base @@ -25,6 +26,10 @@ def merge! @base.infinity_and_beyond = infinity_and_beyond unless infinity_and_beyond.nil? @base.verbose = verbose unless verbose.nil? @base.bundler = bundler unless bundler.nil? + @base.notifications = notifications if notifications.present? + @base.mode = mode if mode.present? + @base.just_watch = just_watch unless just_watch.nil? + @base.focus = focus if focus.present? @base end end diff --git a/lib/infinity_test/core/continuous_test_server.rb b/lib/infinity_test/core/continuous_test_server.rb index a6676da..5388c3c 100644 --- a/lib/infinity_test/core/continuous_test_server.rb +++ b/lib/infinity_test/core/continuous_test_server.rb @@ -3,25 +3,53 @@ module Core class ContinuousTestServer attr_reader :base delegate :binary, :test_files, to: :test_framework - delegate :infinity_and_beyond, :notifications, :extension, to: :base + delegate :infinity_and_beyond, :notifications, :extension, :just_watch, :focus, to: :base def initialize(base) @base = base end def start - run_strategy + run_strategy unless just_watch start_observer end # Run strategy based on the choosed ruby strategy. # def run_strategy - # PENDING: run_before_callbacks + Base.run_before_callbacks(:all) + apply_focus if focus.present? notify(strategy.run) - # PENDING: run_after_callbacks + Base.run_after_callbacks(:all) + ensure + clear_focus + end + + # Apply focus filter to test files + # + def apply_focus + case focus + when :failures + test_framework.test_files = last_failed_files if last_failed_files.present? + when String + test_framework.test_files = focus if File.exist?(focus) + end + end + + # Clear focus after running tests + # + def clear_focus + test_framework.test_files = nil + end + + # Track last failed test files (stored in .infinity_test_failures) + # + def last_failed_files + failures_file = '.infinity_test_failures' + return nil unless File.exist?(failures_file) + File.read(failures_file).split("\n").select { |f| File.exist?(f) }.join(' ') end # Re run strategy changed the changed files. diff --git a/lib/infinity_test/core/load_configuration.rb b/lib/infinity_test/core/load_configuration.rb index 8dd0fd9..e4c838c 100644 --- a/lib/infinity_test/core/load_configuration.rb +++ b/lib/infinity_test/core/load_configuration.rb @@ -4,28 +4,28 @@ class LoadConfiguration attr_accessor :global_file, :project_file def initialize - @global_file = File.expand_path('~/.infinity_test') - @project_file = './.infinity_test' + @global_file = File.expand_path('~/INFINITY_TEST') + @project_file = './INFINITY_TEST' @old_configuration = InfinityTest::OldDSL::Configuration end # Load the Configuration file # - # Command line options can be persisted in a .infinity_test file in a project. - # You can also store a .infinity_test file in your home directory (~/.infinity_test) + # Command line options can be persisted in an INFINITY_TEST file in a project. + # You can also store an INFINITY_TEST file in your home directory (~/INFINITY_TEST) # # Precedence is: # command line - # ./.infinity_test - # ~/.infinity_test + # ./INFINITY_TEST + # ~/INFINITY_TEST # # Example: # - # ~/.infinity_test -> infinity_test { notifications :growl } + # ~/INFINITY_TEST -> infinity_test { notifications :osascript } # - # ./.infinity_test -> infinity_test { notifications :lib_notify } # High Priority + # ./INFINITY_TEST -> infinity_test { notifications :terminal_notifier } # High Priority # - # After the load the Notifications Framework will be Lib Notify + # After the load the Notifications Framework will be Terminal Notifier # def load! load_global_file! diff --git a/lib/infinity_test/core/notifier.rb b/lib/infinity_test/core/notifier.rb index 4705496..921d6c2 100644 --- a/lib/infinity_test/core/notifier.rb +++ b/lib/infinity_test/core/notifier.rb @@ -56,4 +56,4 @@ def images_dir end end end -end \ No newline at end of file +end diff --git a/lib/infinity_test/core/options.rb b/lib/infinity_test/core/options.rb index 3ca1f6d..54b4932 100644 --- a/lib/infinity_test/core/options.rb +++ b/lib/infinity_test/core/options.rb @@ -3,6 +3,7 @@ module Core class Options attr_accessor :arguments, :options_parser, :strategy, :bundler, :verbose attr_accessor :rubies, :specific_options, :test_framework, :framework, :infinity_and_beyond + attr_accessor :notifications, :mode, :just_watch, :focus def initialize(*arguments) @arguments = arguments.flatten.clone @@ -17,7 +18,11 @@ def new_options_parser options_to_added_in_the_command test_framework_to_be_run app_framework + notifications_library + image_mode infinity_and_beyond_option + just_watch_option + focus_option verbose_mode skip_bundler ).each do |option_to_parse| @@ -53,7 +58,7 @@ def options_to_added_in_the_command(option) end def test_framework_to_be_run(option) - option.on('--test library', 'Test Framework to be run. Ex.: auto_discover, rspec, test_unit, bacon.') do |library| + option.on('--test library', 'Test Framework to be run. Ex.: auto_discover, rspec, test_unit.') do |library| @test_framework = library.to_sym end end @@ -64,12 +69,36 @@ def app_framework(option) end end + def notifications_library(option) + option.on('--notifications library', 'Notification library to use. Ex.: auto_discover, osascript, terminal_notifier, notify_send, dunstify.') do |library| + @notifications = library.to_sym + end + end + + def image_mode(option) + option.on('--mode theme', 'Image theme for notifications. Ex.: simpson, faces, fuuu, hands, mario_bros, rails, rubies, street_fighter, toy_story.') do |theme| + @mode = theme.to_sym + end + end + def infinity_and_beyond_option(option) option.on('-n', '--no-infinity-and-beyond', 'Run tests and exit. Useful in a Continuous Integration environment.') do @infinity_and_beyond = false end end + def just_watch_option(option) + option.on('-j', '--just-watch', 'Skip initial test run and only watch for file changes. Useful for large applications.') do + @just_watch = true + end + end + + def focus_option(option) + option.on('-f', '--focus [FILE]', 'Focus on specific tests. Use "failures" for failed tests, or provide a file path.') do |file| + @focus = file == 'failures' ? :failures : file + end + end + def verbose_mode(option) option.on('--no-verbose', "Don't print commands before executing them") do @verbose = false diff --git a/lib/infinity_test/framework/padrino.rb b/lib/infinity_test/framework/padrino.rb index 2e1dc2b..aa55dc0 100644 --- a/lib/infinity_test/framework/padrino.rb +++ b/lib/infinity_test/framework/padrino.rb @@ -1,7 +1,24 @@ module InfinityTest module Framework class Padrino < Base + delegate :test_dir, :test_helper_file, to: :test_framework + + # Add Heuristics to the observer run on pattern changes! + # + # ==== Heuristics + # * Watch app folder (models, controllers, helpers, mailers) and run corresponding tests/specs + # * Watch lib dir and run corresponding tests + # * Watch test/spec dir and run the changed file + # * Watch test/spec helper and run all + # def heuristics + watch_dir('app/models') { |file| run_test(file) } + watch_dir('app/controllers') { |file| run_test(file) } + watch_dir('app/helpers') { |file| run_test(file) } + watch_dir('app/mailers') { |file| run_test(file) } + watch_dir(:lib) { |file| run_test(file) } + watch_dir(test_dir) { |file| run_file(file) } + watch(test_helper_file) { run_all } end # ==== Returns diff --git a/lib/infinity_test/framework/rails.rb b/lib/infinity_test/framework/rails.rb index 4d191e0..2671462 100644 --- a/lib/infinity_test/framework/rails.rb +++ b/lib/infinity_test/framework/rails.rb @@ -1,10 +1,30 @@ module InfinityTest module Framework class Rails < Base + delegate :test_dir, :test_helper_file, to: :test_framework + + # Add Heuristics to the observer run on pattern changes! + # + # ==== Heuristics + # * Watch app/models and run corresponding tests/specs + # * Watch app/controllers and run corresponding tests/specs + # * Watch app/helpers and run corresponding tests/specs + # * Watch app/mailers and run corresponding tests/specs + # * Watch app/jobs and run corresponding tests/specs + # * Watch lib dir and run corresponding tests + # * Watch test/spec dir and run the changed file + # * Watch test/spec helper and run all + # * Watch config/routes.rb and run routing specs + # def heuristics - # watch_dir('app') { |file| RunTest(file, :dir => :spec) } - # watch(:Gemfile) { |file| BundleInstall and RunAll } - # watch(test_framework.test_helper_file) { |file| RunAll } + watch_dir('app/models') { |file| run_test(file) } + watch_dir('app/controllers') { |file| run_test(file) } + watch_dir('app/helpers') { |file| run_test(file) } + watch_dir('app/mailers') { |file| run_test(file) } + watch_dir('app/jobs') { |file| run_test(file) } + watch_dir(:lib) { |file| run_test(file) } + watch_dir(test_dir) { |file| run_file(file) } + watch(test_helper_file) { run_all } end def self.run? diff --git a/lib/infinity_test/framework/shared_example.rb b/lib/infinity_test/framework/shared_example.rb index 4d4f648..4e7aa7d 100644 --- a/lib/infinity_test/framework/shared_example.rb +++ b/lib/infinity_test/framework/shared_example.rb @@ -14,34 +14,34 @@ module SharedExample # end # shared_examples_for 'an infinity test framework' do - it 'should respond to #heuristics' do + it 'responds to #heuristics' do expect(subject).to respond_to(:heuristics) end - it 'should respond to #heuristics' do + it 'responds to #heuristics!' do expect(subject).to respond_to(:heuristics!) end - it 'should respond to .run?' do + it 'responds to .run?' do expect(subject.class).to respond_to(:run?) end - it 'should respond to #base' do + it 'responds to #base' do expect(subject).to respond_to(:base) end - it 'should respond to #test_framework' do + it 'responds to #test_framework' do expect(subject).to respond_to(:test_framework) end - it 'should respond to #strategy' do + it 'responds to #strategy' do expect(subject).to respond_to(:strategy) end - it 'should respond to #observer' do + it 'responds to #observer' do expect(subject).to respond_to(:observer) end end end end -end \ No newline at end of file +end diff --git a/lib/infinity_test/observer/base.rb b/lib/infinity_test/observer/base.rb index 8c3e1c8..8fc88d6 100644 --- a/lib/infinity_test/observer/base.rb +++ b/lib/infinity_test/observer/base.rb @@ -26,17 +26,15 @@ def start def signal Signal.trap('INT') do - if @interrupt + if @interrupt_at && (Time.now - @interrupt_at) < 2 puts " To Infinity and Beyond!" exit else puts " Are you sure? :S ... Interrupt a second time to quit!" - @interrupt = true - Kernel.sleep 1.5 - @interrupt = false + @interrupt_at = Time.now end end end end end -end \ No newline at end of file +end diff --git a/lib/infinity_test/observer/filewatcher.rb b/lib/infinity_test/observer/filewatcher.rb new file mode 100644 index 0000000..1638342 --- /dev/null +++ b/lib/infinity_test/observer/filewatcher.rb @@ -0,0 +1,72 @@ +require 'filewatcher' + +module InfinityTest + module Observer + class Filewatcher < Base + attr_reader :observer, :watch_paths, :patterns + + def initialize(continuous_test_server) + super + @watch_paths = [] + @patterns = {} + end + + # Watch a file or pattern for changes. + # + # ==== Examples + # + # watch('lib/(.*)\.rb') { |file| puts [file.name, file.path, file.match_data] } + # watch('test/test_helper.rb') { run_all() } + # + def watch(pattern_or_file, &block) + pattern = Regexp.new(pattern_or_file.to_s) + @patterns[pattern] = block + end + + # Watch a directory for changes. + # + # ==== Examples + # + # watch_dir(:lib) { |file| run_test(file) } + # watch_dir(:test) { |file| run_file(file) } + # + # watch_dir(:test, :py) { |file| puts [file.name, file.path, file.match_data] } + # watch_dir(:test, :js) { |file| puts [file.name, file.path, file.match_data] } + # + def watch_dir(dir_name, extension = :rb, &block) + watch("^#{dir_name}/*/(.*).#{extension}", &block) + path = "#{dir_name}/**/*.#{extension}" + @watch_paths << path unless @watch_paths.include?(path) + end + + # Start the continuous test server. + # + def start + paths = @watch_paths.empty? ? ['**/*.rb'] : @watch_paths + + @observer = ::Filewatcher.new(paths) + + @observer.watch do |changes| + changes.each do |file_path, _event| + relative_path = file_path.sub("#{Dir.pwd}/", '') + handle_file_change(relative_path) + end + end + rescue Interrupt + @observer.stop if @observer + end + + private + + def handle_file_change(file_path) + @patterns.each do |pattern, block| + if match_data = pattern.match(file_path) + changed_file = InfinityTest::Core::ChangedFile.new(match_data) + block.call(changed_file) + break + end + end + end + end + end +end diff --git a/lib/infinity_test/observer/listen.rb b/lib/infinity_test/observer/listen.rb new file mode 100644 index 0000000..89f0e82 --- /dev/null +++ b/lib/infinity_test/observer/listen.rb @@ -0,0 +1,74 @@ +require 'listen' + +module InfinityTest + module Observer + class Listen < Base + attr_reader :observer, :directories, :patterns + + def initialize(continuous_test_server) + super + @directories = [] + @patterns = {} + end + + # Watch a file or pattern for changes. + # + # ==== Examples + # + # watch('lib/(.*)\.rb') { |file| puts [file.name, file.path, file.match_data] } + # watch('test/test_helper.rb') { run_all() } + # + def watch(pattern_or_file, &block) + pattern = Regexp.new(pattern_or_file.to_s) + @patterns[pattern] = block + end + + # Watch a directory for changes. + # + # ==== Examples + # + # watch_dir(:lib) { |file| run_test(file) } + # watch_dir(:test) { |file| run_file(file) } + # + # watch_dir(:test, :py) { |file| puts [file.name, file.path, file.match_data] } + # watch_dir(:test, :js) { |file| puts [file.name, file.path, file.match_data] } + # + def watch_dir(dir_name, extension = :rb, &block) + watch("^#{dir_name}/*/(.*).#{extension}", &block) + @directories << dir_name.to_s unless @directories.include?(dir_name.to_s) + end + + # Start the continuous test server. + # + def start + dirs = @directories.empty? ? ['.'] : @directories + dirs = dirs.select { |d| File.directory?(d) } + dirs = ['.'] if dirs.empty? + + @observer = ::Listen.to(*dirs) do |modified, added, _removed| + (modified + added).each do |file| + relative_path = file.sub("#{Dir.pwd}/", '') + handle_file_change(relative_path) + end + end + + @observer.start + sleep + rescue Interrupt + @observer.stop if @observer + end + + private + + def handle_file_change(file_path) + @patterns.each do |pattern, block| + if match_data = pattern.match(file_path) + changed_file = InfinityTest::Core::ChangedFile.new(match_data) + block.call(changed_file) + break + end + end + end + end + end +end diff --git a/lib/infinity_test/observer/shared_example.rb b/lib/infinity_test/observer/shared_example.rb index 35900ef..159270f 100644 --- a/lib/infinity_test/observer/shared_example.rb +++ b/lib/infinity_test/observer/shared_example.rb @@ -14,22 +14,22 @@ module SharedExample # end # shared_examples_for 'an infinity test observer' do - it 'should respond to #observer' do + it 'responds to #observer' do expect(subject).to respond_to(:observer) end - it 'should respond to #watch_dir' do + it 'responds to #watch_dir' do expect(subject).to respond_to(:watch_dir) end - it 'should respond to #watch' do + it 'responds to #watch' do expect(subject).to respond_to(:watch) end - it 'should respond to #start' do + it 'responds to #start' do expect(subject).to respond_to(:start) end end end end -end \ No newline at end of file +end diff --git a/lib/infinity_test/observer/watchr.rb b/lib/infinity_test/observer/watchr.rb deleted file mode 100644 index 760ce41..0000000 --- a/lib/infinity_test/observer/watchr.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'watchr' - -module InfinityTest - module Observer - class Watchr < Base - attr_reader :observer - - def initialize(continuous_test_server) - super - @observer = ::Watchr::Script.new - end - - # ==== Examples - # - # watch('lib/(.*)\.rb') { |file| puts [file.name, file.path, file.match_data] } - # watch('test/test_helper.rb') { run_all() } - # - def watch(pattern_or_file, &block) - @observer.watch(pattern_or_file.to_s) do |match_data| - block.call(InfinityTest::Core::ChangedFile.new(match_data)) - end - end - - # ==== Examples - # - # watch_dir(:lib) { |file| RunTest(file) } - # watch_dir(:test) { |file| RunFile(file) } - # - # watch_dir(:test, :py) { |file| puts [file.name, file.path, file.match_data] } - # watch_dir(:test, :js) { |file| puts [file.name, file.path, file.match_data] } - # - def watch_dir(dir_name, extension = :rb, &block) - watch("^#{dir_name}/*/(.*).#{extension}", &block) - end - - # Start the continuous test server. - # - def start - @handler = ::Watchr.handler.new - @controller = ::Watchr::Controller.new(@observer, @handler) - @controller.run - end - end - end -end \ No newline at end of file diff --git a/lib/infinity_test/strategy/rbenv.rb b/lib/infinity_test/strategy/rbenv.rb index 9ce5ff5..4f03af0 100644 --- a/lib/infinity_test/strategy/rbenv.rb +++ b/lib/infinity_test/strategy/rbenv.rb @@ -1,15 +1,31 @@ module InfinityTest module Strategy class Rbenv < Base + attr_reader :continuous_test_server + delegate :binary, :test_files, to: :continuous_test_server + + # Build and run commands for each ruby version specified. + # Uses rbenv's RBENV_VERSION environment variable to run tests in different Ruby environments. + # + # ==== Returns + # String: The command string for all ruby versions joined with && + # def run! + rubies = Core::Base.rubies + + commands = rubies.map do |ruby_version| + "RBENV_VERSION=#{ruby_version} #{command_builder.ruby.option(:S).add(binary).add(test_files).to_s}" + end + + commands.join(' && ') end # ==== Returns - # TrueClass: If the user had the rbenv installed. - # FalseClass: If the user don't had the rbenv installed. + # TrueClass: If the user has rbenv installed AND has specified rubies to test against. + # FalseClass: If rbenv is not installed OR no rubies are specified. # def self.run? - File.exist?(File.expand_path('~/.rbenv')) + Core::Base.rubies.present? && File.exist?(File.expand_path('~/.rbenv')) end end end diff --git a/lib/infinity_test/strategy/rvm.rb b/lib/infinity_test/strategy/rvm.rb index f65f95f..d19fb81 100644 --- a/lib/infinity_test/strategy/rvm.rb +++ b/lib/infinity_test/strategy/rvm.rb @@ -1,18 +1,33 @@ module InfinityTest module Strategy class Rvm < Base + attr_reader :continuous_test_server + delegate :binary, :test_files, to: :continuous_test_server + + # Build and run commands for each ruby version specified. + # Uses RVM's `rvm do` syntax to run tests in different Ruby environments. + # + # ==== Returns + # String: The command string for the first ruby version (others run sequentially) + # def run! - base.rubies.each do |ruby_version| - command_builder.rvm.do.ruby_version.ruby.option(:S).add(test_framework.binary).add(test_framework.test_dir) + rubies = Core::Base.rubies + gemset = Core::Base.gemset + + commands = rubies.map do |ruby_version| + ruby_with_gemset = gemset.present? ? "#{ruby_version}@#{gemset}" : ruby_version + command_builder.rvm.add(ruby_with_gemset).do.ruby.option(:S).add(binary).add(test_files).to_s end + + commands.join(' && ') end # ==== Returns - # TrueClass: If the user had the rvm installed. - # FalseClass: If the user don't had the rvm installed. + # TrueClass: If the user has RVM installed AND has specified rubies to test against. + # FalseClass: If RVM is not installed OR no rubies are specified. # def self.run? - installed_users_home? or installed_system_wide? + Core::Base.rubies.present? && (installed_users_home? || installed_system_wide?) end # ==== Returns diff --git a/lib/infinity_test/strategy/shared_example.rb b/lib/infinity_test/strategy/shared_example.rb index 48131ab..0974965 100644 --- a/lib/infinity_test/strategy/shared_example.rb +++ b/lib/infinity_test/strategy/shared_example.rb @@ -15,18 +15,18 @@ module SharedExample # end # shared_examples_for 'a infinity test strategy' do - it 'should respond to #run!' do + it 'responds to #run!' do expect(subject).to respond_to(:run) end - it 'should respond to .run?' do + it 'responds to .run?' do expect(subject.class).to respond_to(:run?) end - it 'should have Strategy::Base as superclass' do + it 'has Strategy::Base as superclass' do expect(subject.class.superclass).to be InfinityTest::Strategy::Base end end end end -end \ No newline at end of file +end diff --git a/lib/infinity_test/test_framework/bacon.rb b/lib/infinity_test/test_framework/bacon.rb deleted file mode 100644 index d8c46d6..0000000 --- a/lib/infinity_test/test_framework/bacon.rb +++ /dev/null @@ -1,6 +0,0 @@ -module InfinityTest - module TestFramework - class Bacon < Base - end - end -end \ No newline at end of file diff --git a/lib/infinity_test/test_framework/rspec.rb b/lib/infinity_test/test_framework/rspec.rb index 798d4bf..c1eb6f0 100644 --- a/lib/infinity_test/test_framework/rspec.rb +++ b/lib/infinity_test/test_framework/rspec.rb @@ -10,7 +10,11 @@ def test_helper_file end def test_dir - 'spec' + @test_dir ||= 'spec' + end + + def test_dir=(dir) + @test_dir = dir end def test_files @@ -28,6 +32,17 @@ def success? def failure? @failures > 0 end + + def pending? + @pending > 0 + end + + def self.run? + File.exist?('spec') && ( + File.exist?('spec/spec_helper.rb') || + Dir['spec/**/*_spec.rb'].any? + ) + end end end end \ No newline at end of file diff --git a/lib/infinity_test/test_framework/shared_example.rb b/lib/infinity_test/test_framework/shared_example.rb index 1f4849b..547a518 100644 --- a/lib/infinity_test/test_framework/shared_example.rb +++ b/lib/infinity_test/test_framework/shared_example.rb @@ -15,42 +15,42 @@ module SharedExample # end # shared_examples_for 'a infinity test test framework' do - it 'should respond to #test_message=' do + it 'responds to #test_message=' do expect(subject).to respond_to(:test_message=) end - it 'should respond to #test_message' do + it 'responds to #test_message' do expect(subject).to respond_to(:test_message) end - it 'should respond to #succeed?' do + it 'responds to #succeed?' do expect(subject).to respond_to(:success?) end - it 'should respond to #failure?' do + it 'responds to #failure?' do expect(subject).to respond_to(:failure?) end - it 'should respond to #pending?' do + it 'responds to #pending?' do expect(subject).to respond_to(:pending?) end - it 'should respond to #test_helper_file' do + it 'responds to #test_helper_file' do expect(subject).to respond_to(:test_helper_file) end - it 'should respond to #test_dir' do + it 'responds to #test_dir' do expect(subject).to respond_to(:test_dir) end - it 'should respond to #test_dir=' do + it 'responds to #test_dir=' do expect(subject).to respond_to(:test_dir=) end - it 'should respond to #binary' do + it 'responds to #binary' do expect(subject).to respond_to(:binary) end end end end -end \ No newline at end of file +end diff --git a/lib/infinity_test/test_framework/test_unit.rb b/lib/infinity_test/test_framework/test_unit.rb index f2246ad..b3e6e1c 100644 --- a/lib/infinity_test/test_framework/test_unit.rb +++ b/lib/infinity_test/test_framework/test_unit.rb @@ -2,15 +2,55 @@ module InfinityTest module TestFramework class TestUnit < Base def test_dir - 'test' + @test_dir ||= 'test' + end + + def test_dir=(dir) + @test_dir = dir end def test_helper_file File.join(test_dir, 'test_helper.rb') end + def test_files + @test_files || test_dir + end + def binary - '' + 'ruby' + end + + # Patterns for parsing minitest/test-unit output + # Example: "10 runs, 15 assertions, 0 failures, 0 errors, 0 skips" + def patterns + { + :runs => /(\d+) runs,/, + :assertions => /(\d+) assertions,/, + :failures => /(\d+) failures,/, + :errors => /(\d+) errors,/, + :skips => /(\d+) skips/ + } + end + + def success? + @failures.zero? && @errors.zero? && @skips.zero? + end + + def failure? + @failures > 0 || @errors > 0 + end + + def pending? + @skips > 0 + end + + def self.run? + File.exist?('test') && ( + File.exist?('test/test_helper.rb') || + Dir['test/**/*_test.rb'].any? || + Dir['test/**/test_*.rb'].any? + ) end end end diff --git a/spec/infinity_test/core/auto_discover_spec.rb b/spec/infinity_test/core/auto_discover_spec.rb index 054921a..5fb657d 100644 --- a/spec/infinity_test/core/auto_discover_spec.rb +++ b/spec/infinity_test/core/auto_discover_spec.rb @@ -7,7 +7,7 @@ module InfinityTest let(:auto_discover) { AutoDiscover.new(base) } describe '#discover_libraries' do - it 'discover strategy, framework and test framework' do + it 'discovers strategy, framework and test framework' do expect(auto_discover).to receive(:discover_strategy) expect(auto_discover).to receive(:discover_framework) expect(auto_discover).to receive(:discover_test_framework) @@ -19,11 +19,11 @@ module InfinityTest context 'when strategy is auto discover' do before do base.strategy = :auto_discover - Strategy::RubyDefault.stub(:run?).and_return(false) + allow(Strategy::RubyDefault).to receive(:run?).and_return(false) expect(Strategy::Rvm).to receive(:run?).and_return(true) end - it 'change the base strategy' do + it 'changes the base strategy' do auto_discover.discover_strategy expect(base.strategy).to be :rvm end @@ -32,7 +32,7 @@ module InfinityTest context 'when strategy is different from auto discover' do before { base.strategy = :ruby_default } - it 'change anything' do + it 'does not change anything' do auto_discover.discover_strategy expect(base.strategy).to be :ruby_default end @@ -41,9 +41,9 @@ module InfinityTest context 'when do not find any strategy' do before do base.strategy = :auto_discover - Strategy::RubyDefault.stub(:run?).and_return(false) + allow(Strategy::RubyDefault).to receive(:run?).and_return(false) expect(Strategy::Rvm).to receive(:run?).and_return(false) - Strategy::Rbenv.stub(:run?).and_return(false) + allow(Strategy::Rbenv).to receive(:run?).and_return(false) end it 'raises exception' do @@ -56,11 +56,11 @@ module InfinityTest context 'when framework is auto discover' do before do base.framework = :auto_discover - Framework::Rubygems.stub(:run?).and_return(false) + allow(Framework::Rubygems).to receive(:run?).and_return(false) expect(Framework::Rails).to receive(:run?).and_return(true) end - it 'change the base framework' do + it 'changes the base framework' do auto_discover.discover_framework expect(base.framework).to be :rails end @@ -69,7 +69,7 @@ module InfinityTest context 'when framework is different from auto discover' do before { base.framework = :padrino } - it 'change anything' do + it 'does not change anything' do auto_discover.discover_framework expect(base.framework).to be :padrino end @@ -80,25 +80,70 @@ module InfinityTest context 'when framework is auto discover' do before do base.test_framework = :auto_discover - TestFramework::TestUnit.stub(:run?).and_return(false) - TestFramework::Rspec.stub(:run?).and_return(false) - expect(TestFramework::Bacon).to receive(:run?).and_return(true) + allow(TestFramework::TestUnit).to receive(:run?).and_return(false) + expect(TestFramework::Rspec).to receive(:run?).and_return(true) end - it 'change the base framework' do + it 'changes the base framework' do auto_discover.discover_test_framework - expect(base.test_framework).to be :bacon + expect(base.test_framework).to be :rspec end end context 'when framework is different from auto discover' do before { base.test_framework = :test_unit } - it 'change anything' do + it 'does not change anything' do auto_discover.discover_test_framework expect(base.test_framework).to be :test_unit end end end + + describe 'PRIORITY' do + it 'defines priority order for strategies' do + expect(AutoDiscover::PRIORITY[:strategy]).to eq [:rvm, :rbenv, :ruby_default] + end + + it 'defines priority order for frameworks' do + expect(AutoDiscover::PRIORITY[:framework]).to eq [:rails, :padrino, :rubygems] + end + + it 'defines priority order for test frameworks' do + expect(AutoDiscover::PRIORITY[:test_framework]).to eq [:rspec, :test_unit] + end + end + + describe 'priority ordering' do + context 'when multiple strategies match' do + before do + base.strategy = :auto_discover + # Both RVM and RubyDefault would match, but RVM has higher priority + allow(Strategy::Rvm).to receive(:run?).and_return(true) + allow(Strategy::Rbenv).to receive(:run?).and_return(false) + allow(Strategy::RubyDefault).to receive(:run?).and_return(true) + end + + it 'selects the higher priority strategy (RVM over RubyDefault)' do + auto_discover.discover_strategy + expect(base.strategy).to be :rvm + end + end + + context 'when multiple frameworks match' do + before do + base.framework = :auto_discover + # Both Rails and Rubygems would match, but Rails has higher priority + allow(Framework::Rails).to receive(:run?).and_return(true) + allow(Framework::Padrino).to receive(:run?).and_return(false) + allow(Framework::Rubygems).to receive(:run?).and_return(true) + end + + it 'selects the higher priority framework (Rails over Rubygems)' do + auto_discover.discover_framework + expect(base.framework).to be :rails + end + end + end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/core/base_spec.rb b/spec/infinity_test/core/base_spec.rb index f652f3d..c1bc197 100644 --- a/spec/infinity_test/core/base_spec.rb +++ b/spec/infinity_test/core/base_spec.rb @@ -1,10 +1,9 @@ require "spec_helper" -require 'active_support/core_ext/kernel' module InfinityTest describe Base do describe ".setup" do - it "should yield self" do + it "yields self" do Base.setup do |config| expect(config).to be InfinityTest::Base end @@ -12,70 +11,108 @@ module InfinityTest end describe ".using_bundler?" do - it "should return the same bundler accessor" do + it "returns the same bundler accessor" do expect(Base.using_bundler?).to equal Base.bundler end end describe "#verbose?" do - it "should return the same verbose accessor" do + it "returns the same verbose accessor" do expect(Base.verbose?).to equal Base.verbose end end describe ".observer" do - it "should have watchr as default observer" do - expect(Base.observer).to equal :watchr + it "has listen as default observer" do + expect(Base.observer).to equal :listen end end describe ".ignore_test_files" do - it "should not have test files to ignore as default" do + it "does not have test files to ignore as default" do expect(Base.ignore_test_files).to eql [] end end describe "#ignore_test_folders" do - it "should not ignore test folders as default" do + it "does not ignore test folders as default" do expect(Base.ignore_test_folders).to eql [] end end describe ".before" do - before { pending } - let(:proc) { Proc.new { 'To Infinity and beyond!' } } + after { Base.clear_callbacks! } - it "should create before callback instance and push to the callback accessor" do - expect(BeforeCallback).to receive(:new).with(:all, &proc).once.and_return(:foo) - before_callback = Base.before(:all, &proc) - expect(before_callback).to be :foo - expect(Base.callbacks).to be_include before_callback + it "creates before callback instance and pushes to the callback accessor" do + before_callback = Base.before(:all) { 'To Infinity and beyond!' } + expect(before_callback).to be_a Core::Callback + expect(before_callback.type).to eq :before + expect(before_callback.scope).to eq :all + expect(Base.callbacks).to include before_callback + end + + it "defaults scope to :all" do + before_callback = Base.before { 'test' } + expect(before_callback.scope).to eq :all end end describe ".after" do - before { pending } - let(:proc) { Proc.new {}} + after { Base.clear_callbacks! } + + it "creates after callback instance and pushes to the callback accessor" do + after_callback = Base.after(:each_ruby) { 'done' } + expect(after_callback).to be_a Core::Callback + expect(after_callback.type).to eq :after + expect(after_callback.scope).to eq :each_ruby + expect(Base.callbacks).to include after_callback + end + end + + describe ".run_before_callbacks" do + after { Base.clear_callbacks! } + + it "runs all before callbacks for the given scope" do + results = [] + Base.before(:all) { results << 'first' } + Base.before(:all) { results << 'second' } + Base.before(:each_ruby) { results << 'each_ruby' } + + Base.run_before_callbacks(:all) + expect(results).to eq ['first', 'second'] + end + end + + describe ".run_after_callbacks" do + after { Base.clear_callbacks! } + + it "runs all after callbacks for the given scope" do + results = [] + Base.after(:all) { results << 'first' } + Base.after(:all) { results << 'second' } + + Base.run_after_callbacks(:all) + expect(results).to eq ['first', 'second'] + end + end - it "should create before callback instance and push to the callback accessor" do - expect(AfterCallback).to receive(:new).with(:each, &proc).once.and_return(:foo) - after_callback = Base.after(:each, &proc) - expect(after_callback).to be :foo - expect(Base.callbacks).to be_include after_callback + describe ".just_watch" do + it "defaults to false" do + expect(Base.just_watch).to eq false end end describe ".notifications" do - it "should set the notification class accessor" do + it "sets the notification class accessor" do silence_stream(STDOUT) do - Base.notifications(:growl) - expect(Base.notifications).to be :growl + Base.notifications(:osascript) + expect(Base.notifications).to be :osascript end end - it "should set the images" do + it "sets the images" do silence_stream(STDOUT) do - Base.notifications(:growl) do + Base.notifications(:osascript) do show_images :success_image => 'foo', :failure_image => 'bar', :pending_image => 'baz' end end @@ -87,9 +124,9 @@ module InfinityTest Base.pending_image = nil end - it "should set the mode" do + it "sets the mode" do silence_stream(STDOUT) do - Base.notifications(:growl) do + Base.notifications(:osascript) do show_images :mode => :mortal_kombat end end @@ -116,42 +153,42 @@ module InfinityTest Base.gemset = @gemset end - it "should set the rubies" do + it "sets the rubies" do silence_stream(STDOUT) do Base.use :rubies => %w(foo bar) end expect(Base.rubies).to eql %w(foo bar) end - it "should set the specific options" do + it "sets the specific options" do silence_stream(STDOUT) do Base.use :specific_options => '-J -Ilib -Itest' end expect(Base.specific_options).to eql '-J -Ilib -Itest' end - it "should set the test framework" do + it "sets the test framework" do silence_stream(STDOUT) do Base.use :test_framework => :rspec end expect(Base.test_framework).to be :rspec end - it "should set the app framework" do + it "sets the app framework" do silence_stream(STDOUT) do Base.use :app_framework => :rails end expect(Base.framework).to be :rails end - it "should set the verbose mode" do + it "sets the verbose mode" do silence_stream(STDOUT) do Base.use :verbose => false end - expect(Base.verbose).to equal false # I choose to don't use should be_false + expect(Base.verbose).to equal false end - it "should set the gemset" do + it "sets the gemset" do silence_stream(STDOUT) do Base.use :gemset => 'infinity_test' end @@ -160,14 +197,14 @@ module InfinityTest end describe ".heuristics" do - it "should need to see." do - pending 'Need to see what to do with the patterns to watch' + it "accepts a block" do + expect { Base.heuristics {} }.to_not raise_exception end end describe ".replace_patterns" do - it "should need to see" do - pending 'Need to see how improve this method.' + it "accepts a block" do + expect { Base.replace_patterns {} }.to_not raise_exception end end @@ -175,7 +212,7 @@ module InfinityTest let(:options) { Object.new } let(:configuration_merge) { Object.new } - it "should call merge on the configuration merge object" do + it "calls merge on the configuration merge object" do expect(ConfigurationMerge).to receive(:new).with(Core::Base, options).and_return(configuration_merge) expect(configuration_merge).to receive(:merge!) Core::Base.merge!(options) @@ -183,7 +220,7 @@ module InfinityTest end describe ".clear" do - it "should call clear_terminal method" do + it "calls clear_terminal method" do silence_stream(STDOUT) do expect(Base).to receive(:clear_terminal).and_return(true) Base.clear(:terminal) @@ -192,7 +229,7 @@ module InfinityTest end describe ".clear_terminal" do - it "should call system clear" do + it "calls system clear" do silence_stream(STDOUT) do expect(Base).to receive(:system).with('clear').and_return(true) Base.clear_terminal @@ -200,4 +237,4 @@ module InfinityTest end end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/core/callback_spec.rb b/spec/infinity_test/core/callback_spec.rb new file mode 100644 index 0000000..47bed18 --- /dev/null +++ b/spec/infinity_test/core/callback_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +module InfinityTest + module Core + describe Callback do + describe '#initialize' do + it 'creates a before callback with default scope' do + callback = Callback.new(:before) { 'test' } + expect(callback.type).to eq :before + expect(callback.scope).to eq :all + end + + it 'creates an after callback with custom scope' do + callback = Callback.new(:after, :each_ruby) { 'test' } + expect(callback.type).to eq :after + expect(callback.scope).to eq :each_ruby + end + + it 'raises error for invalid scope' do + expect { Callback.new(:before, :invalid) { 'test' } }.to raise_error(ArgumentError) + end + end + + describe '#call' do + it 'executes the block without arguments' do + result = nil + callback = Callback.new(:before) { result = 'executed' } + callback.call + expect(result).to eq 'executed' + end + + it 'executes the block with environment argument' do + result = nil + callback = Callback.new(:before, :each_ruby) { |env| result = env[:ruby_version] } + callback.call(ruby_version: '3.0.0') + expect(result).to eq '3.0.0' + end + end + + describe '#before?' do + it 'returns true for before callbacks' do + callback = Callback.new(:before) { 'test' } + expect(callback).to be_before + end + + it 'returns false for after callbacks' do + callback = Callback.new(:after) { 'test' } + expect(callback).not_to be_before + end + end + + describe '#after?' do + it 'returns true for after callbacks' do + callback = Callback.new(:after) { 'test' } + expect(callback).to be_after + end + + it 'returns false for before callbacks' do + callback = Callback.new(:before) { 'test' } + expect(callback).not_to be_after + end + end + + describe '#all?' do + it 'returns true for all scope' do + callback = Callback.new(:before, :all) { 'test' } + expect(callback).to be_all + end + + it 'returns false for each_ruby scope' do + callback = Callback.new(:before, :each_ruby) { 'test' } + expect(callback).not_to be_all + end + end + + describe '#each_ruby?' do + it 'returns true for each_ruby scope' do + callback = Callback.new(:before, :each_ruby) { 'test' } + expect(callback).to be_each_ruby + end + + it 'returns false for all scope' do + callback = Callback.new(:before, :all) { 'test' } + expect(callback).not_to be_each_ruby + end + end + end + end +end diff --git a/spec/infinity_test/core/command_builder_spec.rb b/spec/infinity_test/core/command_builder_spec.rb index 5139a22..4c0538a 100644 --- a/spec/infinity_test/core/command_builder_spec.rb +++ b/spec/infinity_test/core/command_builder_spec.rb @@ -4,35 +4,35 @@ module InfinityTest module Core describe CommandBuilder do describe "#add" do - it "should add the argument in the command" do + it "adds the argument in the command" do expect(subject.ruby.option(:S).add(:rspec).add(:spec)).to eq 'ruby -S rspec spec' end end describe "#option" do - it "should put options with one dash and be possible to add more keywords" do + it "puts options with one dash and allows adding more keywords" do expect(subject.bundle.exec.ruby.option(:S).rspec).to eq 'bundle exec ruby -S rspec' end end describe "#opt" do - it "should put double dash options and be possible to add keywords" do + it "puts double dash options and allows adding keywords" do expect(subject.ruby.opt(:copyright)).to eq 'ruby --copyright' end end describe '#method_missing' do - it "should put space after every keyword" do + it "puts space after every keyword" do expect(subject.bundle.exec.ruby.some_file).to eq 'bundle exec ruby some_file' end end describe "#respond_to?" do - it "should respond to enything because missing methods will build the command" do - expect(subject.respond_to?(:foo)).to be_true - expect(subject.respond_to?(:bar)).to be_true + it "responds to anything because missing methods will build the command" do + expect(subject.respond_to?(:foo)).to be true + expect(subject.respond_to?(:bar)).to be true end end end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/core/configuration_merge_spec.rb b/spec/infinity_test/core/configuration_merge_spec.rb index 5af7e98..2762797 100644 --- a/spec/infinity_test/core/configuration_merge_spec.rb +++ b/spec/infinity_test/core/configuration_merge_spec.rb @@ -21,99 +21,99 @@ module Core subject { ConfigurationMerge.new(base, options) } describe "#merge!" do - it "should keep the strategy when options strategy is blank" do + it "keeps the strategy when options strategy is blank" do subject.merge! expect(base.strategy).to be :auto_discover end - it "should merge the strategies" do + it "merges the strategies" do expect(options).to receive(:strategy).twice.and_return(:rbenv) subject.merge! expect(base.strategy).to be :rbenv end - it "should keep the rubies when options rubies is nil" do + it "keeps the rubies when options rubies is nil" do base.rubies = %w(jruby ree) expect(options).to receive(:rubies).and_return(nil) subject.merge! expect(base.rubies).to eq %w(jruby ree) end - it "should overwrite the rubies when options rubies is empty" do + it "overwrites the rubies when options rubies is empty" do base.rubies = %w(jruby ree) expect(options).to receive(:rubies).twice.and_return([]) subject.merge! expect(base.rubies).to be_blank end - it "should merge the rubies" do + it "merges the rubies" do expect(options).to receive(:rubies).twice.and_return(%w(ree jruby)) subject.merge! expect(base.rubies).to eql %w(ree jruby) end - it "should keep the test framework when options test framework is blank" do + it "keeps the test framework when options test framework is blank" do subject.merge! expect(base.test_framework).to be :auto_discover end - it "should merge the test library" do + it "merges the test library" do expect(options).to receive(:test_framework).twice.and_return(:rspec) subject.merge! expect(base.test_framework).to be :rspec end - it "should keep the framework when options framework is blank" do + it "keeps the framework when options framework is blank" do subject.merge! expect(base.framework).to be :auto_discover end - it "should merge the framework" do + it "merges the framework" do expect(options).to receive(:framework).twice.and_return(:rails) subject.merge! expect(base.framework).to be :rails end - it "should keep the specific option when options specific is blank" do + it "keeps the specific option when options specific is blank" do subject.merge! expect(base.specific_options).to eql '' end - it "should merge the specific options" do + it "merges the specific options" do expect(options).to receive(:specific_options).twice.and_return('-J -Ilib -Itest') subject.merge! expect(base.specific_options).to eql '-J -Ilib -Itest' end - it "should merge with the infinity test and beyond" do + it "merges with the infinity test and beyond" do expect(options).to receive(:infinity_and_beyond).twice.and_return(false) subject.merge! expect(base.infinity_and_beyond).to be false end - it "should keep the base default if option infinity test and beyond is nil" do + it "keeps the base default if option infinity test and beyond is nil" do expect(options).to receive(:infinity_and_beyond).and_return(nil) subject.merge! expect(base.infinity_and_beyond).to be true end - it "should keep the verbose mode when verbose mode is blank" do + it "keeps the verbose mode when verbose mode is blank" do subject.merge! - expect(base.verbose).to be_true + expect(base.verbose).to be true end - it "should merge the verbose mode" do + it "merges the verbose mode" do expect(options).to receive(:verbose).twice.and_return(false) subject.merge! expect(base.verbose).to be false end - it "should keep the bundler default when bundler is blank" do + it "keeps the bundler default when bundler is blank" do subject.merge! expect(base.bundler).to be true end - it "should merge the verbose mode" do + it "merges the bundler mode" do expect(options).to receive(:bundler).twice.and_return(false) subject.merge! expect(base.bundler).to be false @@ -121,4 +121,4 @@ module Core end end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/core/continuous_test_server_spec.rb b/spec/infinity_test/core/continuous_test_server_spec.rb index dee5a51..607c2d6 100644 --- a/spec/infinity_test/core/continuous_test_server_spec.rb +++ b/spec/infinity_test/core/continuous_test_server_spec.rb @@ -7,7 +7,7 @@ module Core let(:continuous_test_server) { ContinuousTestServer.new(base) } describe '#start!' do - it 'run strategy, start observer' do + it 'runs strategy and starts observer' do expect(continuous_test_server).to receive(:run_strategy) expect(continuous_test_server).to receive(:start_observer) continuous_test_server.start @@ -18,11 +18,11 @@ module Core context 'when base configuration as infinity and beyond' do before do expect(base).to receive(:infinity_and_beyond).and_return(true) - base.stub(:framework).and_return(:rails) - base.stub(:observer).and_return(:watchr) + allow(base).to receive(:framework).and_return(:rails) + allow(base).to receive(:observer).and_return(:listen) end - it 'add framework heuristics and start the observer' do + it 'adds framework heuristics and starts the observer' do expect(continuous_test_server.framework).to receive(:heuristics!) expect(continuous_test_server.observer).to receive(:start!) continuous_test_server.start_observer @@ -32,10 +32,10 @@ module Core context 'when base configuration is not infinity and beyond' do before do expect(base).to receive(:infinity_and_beyond).and_return(false) - base.stub(:framework).and_return(:rails) + allow(base).to receive(:framework).and_return(:rails) end - it 'do not start the observer' do + it 'does not start the observer' do expect(continuous_test_server.framework).to_not receive(:heuristics!) expect(continuous_test_server.observer).to_not receive(:start!) continuous_test_server.start_observer @@ -49,14 +49,14 @@ module Core context 'when have notification library' do let(:test_framework) { double } - let(:base) { double(notifications: :growl) } + let(:base) { double(notifications: :auto_discover) } before do expect(continuous_test_server).to receive(:test_framework).exactly(:twice).and_return(test_framework) expect(test_framework).to receive(:test_message=).with(test_message) end - it 'instantiante a notifier passing the strategy result and the continuous server' do + it 'instantiates a notifier passing the strategy result and the continuous server' do expect(Core::Notifier).to receive(:new).and_return(double(notify: true)) continuous_test_server.notify(test_message) end @@ -65,7 +65,7 @@ module Core context 'when do not have notification library' do let(:base) { double(notifications: nil) } - it 'instantiante a notifier passing the strategy result and the continuous server' do + it 'does not instantiate a notifier' do expect(Core::Notifier).to_not receive(:new) continuous_test_server.notify(test_message) end @@ -79,7 +79,7 @@ module Core expect(continuous_test_server).to receive(:test_framework).twice.and_return(test_framework) end - it 'run strategy agains and ensure the test files is nil after' do + it 'runs strategy again and ensures the test files is nil after' do expect(test_framework).to receive(:test_files=).twice expect(continuous_test_server).to receive(:run_strategy) continuous_test_server.rerun_strategy('base_test.py') @@ -113,4 +113,4 @@ module Core end end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/core/load_configuration_spec.rb b/spec/infinity_test/core/load_configuration_spec.rb index 4ecdfb8..dc7a5d1 100644 --- a/spec/infinity_test/core/load_configuration_spec.rb +++ b/spec/infinity_test/core/load_configuration_spec.rb @@ -3,7 +3,7 @@ module InfinityTest describe LoadConfiguration do describe "#load!" do - it "should load global file and project file" do + it "loads global file and project file" do expect(subject).to receive(:load_global_file!).and_return(true) expect(subject).to receive(:load_project_file!).and_return(true) subject.load! @@ -11,7 +11,7 @@ module InfinityTest end describe "#load_global_file!" do - it "should load the global file" do + it "loads the global file" do subject.global_file = 'foo' expect(subject).to receive(:load_file).with('foo').and_return(true) subject.load_global_file! @@ -19,7 +19,7 @@ module InfinityTest end describe "#load_project_file!" do - it "should load the project file" do + it "loads the project file" do subject.project_file = 'bar' expect(subject).to receive(:load_file).with('bar').and_return(true) subject.load_project_file! @@ -27,17 +27,17 @@ module InfinityTest end describe "#load_file" do - it "should load if file exist" do + it "loads if file exists" do expect(File).to receive(:exist?).with('bar').and_return(true) expect(subject).to receive(:load).with('bar').and_return(true) - expect(subject.load_file('bar')).to be_true + expect(subject.load_file('bar')).to be true end - it "should not load if file dont exist" do + it "does not load if file does not exist" do expect(File).to receive(:exist?).with('baz').and_return(false) expect(subject).to_not receive(:load) subject.load_file('baz') end end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/core/notifier_spec.rb b/spec/infinity_test/core/notifier_spec.rb index c1e2fe0..2097185 100644 --- a/spec/infinity_test/core/notifier_spec.rb +++ b/spec/infinity_test/core/notifier_spec.rb @@ -5,7 +5,7 @@ module Core describe Notifier do let(:test_framework) { double } - subject(:notifier) { Notifier.new(test_framework: test_framework, library: :growl) } + subject(:notifier) { Notifier.new(test_framework: test_framework, library: :auto_discover) } describe '#image' do before do @@ -47,7 +47,7 @@ module Core describe '#library' do it 'returns the primitive value' do - expect(notifier.library).to be :growl + expect(notifier.library).to be :auto_discover end end @@ -126,6 +126,26 @@ module Core end end end + + describe '#notify' do + let(:notification_builder) { double } + + before do + expect(Core::Base).to receive(:mode).and_return(:simpson) + expect(test_framework).to receive(:success?).and_return(true) + expect(test_framework).to receive(:test_message).and_return('5 examples, 0 failures') + end + + it 'sends a notification with title, message and image' do + expect(notifier).to receive(:auto_discover).and_return(notification_builder) + expect(notification_builder).to receive(:title).with(RUBY_VERSION).and_return(notification_builder) + expect(notification_builder).to receive(:message).with('5 examples, 0 failures').and_return(notification_builder) + expect(notification_builder).to receive(:image).and_return(notification_builder) + expect(notification_builder).to receive(:notify) + + notifier.notify + end + end end end end \ No newline at end of file diff --git a/spec/infinity_test/core/options_spec.rb b/spec/infinity_test/core/options_spec.rb index e6dad18..630ee67 100644 --- a/spec/infinity_test/core/options_spec.rb +++ b/spec/infinity_test/core/options_spec.rb @@ -4,101 +4,165 @@ module InfinityTest describe Options do describe "#parse!" do describe "#strategy" do - it "should parse the --ruby options with rvm" do + it "parses the --ruby options with rvm" do expect(parse('--ruby', 'rvm').strategy).to be :rvm end - it "should parse the --ruby options with rbenv" do + it "parses the --ruby options with rbenv" do expect(parse('--ruby', 'rbenv').strategy).to be :rbenv end end describe "#rubies" do - it "should pass the ruby versions" do + it "passes the ruby versions" do expect(parse('--rubies=ree,jruby').rubies).to eql %w(ree jruby) end - it "should have empty rubies when pass rubies= without versions" do + it "has empty rubies when pass rubies= without versions" do expect(parse('--rubies=').rubies).to eql [] end - it "should be nil when don't pass the rubies option" do + it "is nil when rubies option is not passed" do expect(parse.rubies).to be_nil end end describe "#infinity_and_beyond" do - it "should return false when setting --no-infinity-and-beyond" do + it "returns false when setting --no-infinity-and-beyond" do expect(parse('--no-infinity-and-beyond').infinity_and_beyond).to equal false expect(parse('-n').infinity_and_beyond).to equal false end - it "should return nil when not setting the --no-infinity-and-beyond" do + it "returns nil when not setting the --no-infinity-and-beyond" do expect(parse.infinity_and_beyond).to be_nil end end describe "#specific_options" do - it "should parse the options" do + it "parses the options" do expect(parse('--options=-J-Ilib-Itest').specific_options).to eql '-J -Ilib -Itest' end end describe "#test_framework" do - it "should parse the test framework as rspec" do + it "parses the test framework as rspec" do expect(parse('--test', 'rspec').test_framework).to be :rspec end - it "should parse the test framework as bacon" do - expect(parse('--test', 'bacon').test_framework).to be :bacon - end - - it "should parse the test framework as test_unit" do + it "parses the test framework as test_unit" do expect(parse('--test', 'test_unit').test_framework).to be :test_unit end - it "should parse the test framework as other" do + it "parses the test framework as other" do expect(parse('--test', 'other').test_framework).to be :other end end describe "#framework" do - it "should parse the app framework as rails" do + it "parses the app framework as rails" do expect(parse('--framework', 'rails').framework).to be :rails end - it "should parse the app framework as rubygems" do + it "parses the app framework as rubygems" do expect(parse('--framework', 'rubygems').framework).to be :rubygems end - it "should parse the app framework as other" do + it "parses the app framework as other" do expect(parse('--framework', 'other').framework).to be :other end end describe "#verbose?" do - it "should return nil when dont pass nothing" do + it "returns nil when nothing is passed" do expect(parse.verbose?).to be_nil end - it "should not be verbose whe pass the option --no-verbose" do + it "is not verbose when passing the option --no-verbose" do expect(parse('--no-verbose')).not_to be_verbose end end describe "#bundler?" do - it "should not use bundler when passing this option" do + it "does not use bundler when passing this option" do expect(parse('--no-bundler')).not_to be_bundler end - it "should return nil when dont pass nothing" do + it "returns nil when nothing is passed" do expect(parse.bundler?).to be_nil end end + + describe "#notifications" do + it "parses the notifications library as auto_discover" do + expect(parse('--notifications', 'auto_discover').notifications).to be :auto_discover + end + + it "parses the notifications library as osascript" do + expect(parse('--notifications', 'osascript').notifications).to be :osascript + end + + it "parses the notifications library as terminal_notifier" do + expect(parse('--notifications', 'terminal_notifier').notifications).to be :terminal_notifier + end + + it "returns nil when nothing is passed" do + expect(parse.notifications).to be_nil + end + end + + describe "#mode" do + it "parses the image mode as simpson" do + expect(parse('--mode', 'simpson').mode).to be :simpson + end + + it "parses the image mode as faces" do + expect(parse('--mode', 'faces').mode).to be :faces + end + + it "parses the image mode as rails" do + expect(parse('--mode', 'rails').mode).to be :rails + end + + it "returns nil when nothing is passed" do + expect(parse.mode).to be_nil + end + end + + describe "#just_watch" do + it "returns true when setting --just-watch" do + expect(parse('--just-watch').just_watch).to eq true + end + + it "returns true when setting -j" do + expect(parse('-j').just_watch).to eq true + end + + it "returns nil when nothing is passed" do + expect(parse.just_watch).to be_nil + end + end + + describe "#focus" do + it "parses --focus with file path" do + expect(parse('--focus', 'spec/models/user_spec.rb').focus).to eq 'spec/models/user_spec.rb' + end + + it "parses -f with file path" do + expect(parse('-f', 'spec/models/user_spec.rb').focus).to eq 'spec/models/user_spec.rb' + end + + it "parses --focus failures as symbol" do + expect(parse('--focus', 'failures').focus).to eq :failures + end + + it "returns nil when nothing is passed" do + expect(parse.focus).to be_nil + end + end end def parse(*args) InfinityTest::Options.new(args.flatten).parse! end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/framework/padrino_spec.rb b/spec/infinity_test/framework/padrino_spec.rb index 45ccdf0..12afd87 100644 --- a/spec/infinity_test/framework/padrino_spec.rb +++ b/spec/infinity_test/framework/padrino_spec.rb @@ -3,25 +3,34 @@ module InfinityTest module Framework describe Padrino do - subject { Padrino.new(Core::Base) } + let(:observer) { double('Observer') } + let(:test_framework) { double('TestFramework') } + let(:continuous_test_server) { double('ContinuousTestServer', observer: observer, test_framework: test_framework) } + subject { Padrino.new(continuous_test_server) } + describe "#heuristics" do - before { pending } - it "should add heuristics" do + it "adds heuristics" do + # 6 watch_dir calls: models, controllers, helpers, mailers, lib, test_dir + expect(observer).to receive(:watch_dir).exactly(6) + # 1 watch call for test_helper_file + expect(observer).to receive(:watch) + expect(test_framework).to receive(:test_helper_file) + expect(test_framework).to receive(:test_dir) expect { subject.heuristics }.to_not raise_exception end end describe ".run?" do - it "should return true if find the config/apps.rb" do + it "returns true if config/apps.rb exists" do expect(File).to receive(:exist?).with(File.expand_path('./config/apps.rb')).and_return(true) expect(Padrino).to be_run end - it "should return false if don't find the config/apps.rb" do + it "returns false if config/apps.rb does not exist" do expect(File).to receive(:exist?).with(File.expand_path('./config/apps.rb')).and_return(false) expect(Padrino).not_to be_run end end end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/framework/rails_spec.rb b/spec/infinity_test/framework/rails_spec.rb index 1b72af5..4312afb 100644 --- a/spec/infinity_test/framework/rails_spec.rb +++ b/spec/infinity_test/framework/rails_spec.rb @@ -3,27 +3,34 @@ module InfinityTest module Framework describe Rails do - subject { Rails.new(Core::Base) } + let(:observer) { double('Observer') } + let(:test_framework) { double('TestFramework') } + let(:continuous_test_server) { double('ContinuousTestServer', observer: observer, test_framework: test_framework) } + subject { Rails.new(continuous_test_server) } describe "#heuristics" do - before { pending } - - it "should add heuristics" do + it "adds heuristics" do + # 6 watch_dir calls: models, controllers, helpers, mailers, jobs, lib, test_dir + expect(observer).to receive(:watch_dir).exactly(7) + # 1 watch call for test_helper_file + expect(observer).to receive(:watch) + expect(test_framework).to receive(:test_helper_file) + expect(test_framework).to receive(:test_dir) expect { subject.heuristics }.to_not raise_exception end end describe ".run?" do - it "should return true if exist the config/enviroment.rb file" do + it "returns true if config/environment.rb exists" do expect(File).to receive(:exist?).with(File.expand_path('./config/environment.rb')).and_return(true) expect(Rails).to be_run end - it "should return false if don't exist the config/enviroment.rb file" do + it "returns false if config/environment.rb does not exist" do expect(File).to receive(:exist?).with(File.expand_path('./config/environment.rb')).and_return(false) expect(Rails).not_to be_run end end end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/framework/rubygems_spec.rb b/spec/infinity_test/framework/rubygems_spec.rb index dde3e14..32476be 100644 --- a/spec/infinity_test/framework/rubygems_spec.rb +++ b/spec/infinity_test/framework/rubygems_spec.rb @@ -9,7 +9,7 @@ module Framework subject { Rubygems.new(continuous_test_server) } describe "#heuristics" do - it "should add heuristics" do + it "adds heuristics" do expect(observer).to receive(:watch_dir).exactly(2) expect(observer).to receive(:watch) expect(test_framework).to receive(:test_helper_file) @@ -19,12 +19,12 @@ module Framework end describe ".run?" do - it "should return true if have a .gemspec in the user current dir" do + it "returns true if there is a .gemspec in the user current dir" do expect(Dir).to receive(:[]).with('*.gemspec').and_return(['infinity_test.gemspec']) expect(Rubygems).to be_run end - it "should return false if don't have a .gemspec in the user current dir" do + it "returns false if there is no .gemspec in the user current dir" do expect(Dir).to receive(:[]).with('*.gemspec').and_return([]) expect(Rubygems).not_to be_run end diff --git a/spec/infinity_test/observer/base_spec.rb b/spec/infinity_test/observer/base_spec.rb new file mode 100644 index 0000000..476d92c --- /dev/null +++ b/spec/infinity_test/observer/base_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' + +module InfinityTest + module Observer + describe Base do + let(:continuous_server) { double } + subject { Base.new(continuous_server) } + + describe "#initialize" do + it "sets the continuous_test_server" do + expect(subject.continuous_test_server).to eq(continuous_server) + end + end + + describe "#watch" do + it "raises NotImplementedError" do + expect { subject.watch('pattern') {} }.to raise_error(NotImplementedError) + end + end + + describe "#watch_dir" do + it "raises NotImplementedError" do + expect { subject.watch_dir(:lib, :rb) {} }.to raise_error(NotImplementedError) + end + end + + describe "#start" do + it "raises NotImplementedError" do + expect { subject.start }.to raise_error(NotImplementedError) + end + end + + describe "#start!" do + it "calls signal and start" do + expect(subject).to receive(:signal) + expect(subject).to receive(:start) + subject.start! + end + end + + describe "#signal" do + it "traps INT signal" do + expect(Signal).to receive(:trap).with('INT') + subject.signal + end + + context "interrupt handling logic" do + let(:handler) do + handler_block = nil + allow(Signal).to receive(:trap) { |_, &block| handler_block = block } + subject.signal + handler_block + end + + it "shows warning on first interrupt" do + expect(subject).to receive(:puts).with(" Are you sure? :S ... Interrupt a second time to quit!") + handler.call + expect(subject.instance_variable_get(:@interrupt_at)).to be_within(1).of(Time.now) + end + + it "exits on second interrupt within 2 seconds" do + subject.instance_variable_set(:@interrupt_at, Time.now) + expect(subject).to receive(:puts).with(" To Infinity and Beyond!") + expect(subject).to receive(:exit) + handler.call + end + + it "resets timer if second interrupt is after 2 seconds" do + subject.instance_variable_set(:@interrupt_at, Time.now - 3) + expect(subject).to receive(:puts).with(" Are you sure? :S ... Interrupt a second time to quit!") + handler.call + expect(subject.instance_variable_get(:@interrupt_at)).to be_within(1).of(Time.now) + end + end + end + end + end +end diff --git a/spec/infinity_test/observer/filewatcher_spec.rb b/spec/infinity_test/observer/filewatcher_spec.rb new file mode 100644 index 0000000..39e73e9 --- /dev/null +++ b/spec/infinity_test/observer/filewatcher_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +module InfinityTest + module Observer + describe Filewatcher do + let(:continuous_server) { double } + subject { Filewatcher.new(continuous_server) } + + it_should_behave_like 'an infinity test observer' + + describe "#patterns" do + it "starts as an empty hash" do + expect(subject.patterns).to eq({}) + end + end + + describe "#watch_paths" do + it "starts as an empty array" do + expect(subject.watch_paths).to eq([]) + end + end + + describe "#watch" do + it "adds a pattern with its block to patterns" do + block = proc { |file| file } + subject.watch('lib/(.*)\.rb', &block) + expect(subject.patterns.keys.first).to be_a(Regexp) + expect(subject.patterns.values.first).to eq(block) + end + end + + describe "#watch_dir" do + it "adds a glob pattern to watch_paths" do + subject.watch_dir(:spec) + expect(subject.watch_paths).to include('spec/**/*.rb') + end + + it "adds a pattern to patterns" do + subject.watch_dir(:spec) + expect(subject.patterns.keys.first.source).to eq('^spec/*/(.*).rb') + end + + it "accepts a custom extension" do + subject.watch_dir(:spec, :py) + expect(subject.watch_paths).to include('spec/**/*.py') + expect(subject.patterns.keys.first.source).to eq('^spec/*/(.*).py') + end + end + end + end +end diff --git a/spec/infinity_test/observer/listen_spec.rb b/spec/infinity_test/observer/listen_spec.rb new file mode 100644 index 0000000..441fa72 --- /dev/null +++ b/spec/infinity_test/observer/listen_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +module InfinityTest + module Observer + describe Listen do + let(:continuous_server) { double } + subject { Listen.new(continuous_server) } + + it_should_behave_like 'an infinity test observer' + + describe "#patterns" do + it "starts as an empty hash" do + expect(subject.patterns).to eq({}) + end + end + + describe "#directories" do + it "starts as an empty array" do + expect(subject.directories).to eq([]) + end + end + + describe "#watch" do + it "adds a pattern with its block to patterns" do + block = proc { |file| file } + subject.watch('lib/(.*)\.rb', &block) + expect(subject.patterns.keys.first).to be_a(Regexp) + expect(subject.patterns.values.first).to eq(block) + end + end + + describe "#watch_dir" do + it "adds a pattern to patterns" do + subject.watch_dir(:spec) + expect(subject.patterns.keys.first.source).to eq('^spec/*/(.*).rb') + end + + it "adds the directory to directories" do + subject.watch_dir(:spec) + expect(subject.directories).to include('spec') + end + + it "accepts a custom extension" do + subject.watch_dir(:spec, :py) + expect(subject.patterns.keys.first.source).to eq('^spec/*/(.*).py') + end + end + end + end +end diff --git a/spec/infinity_test/observer/watchr_spec.rb b/spec/infinity_test/observer/watchr_spec.rb deleted file mode 100644 index 3b977a4..0000000 --- a/spec/infinity_test/observer/watchr_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'spec_helper' - -module InfinityTest - module Observer - describe Watchr do - let(:continuous_server) { double } - subject { Watchr.new(continuous_server)} - it_should_behave_like 'an infinity test observer' - - describe "#observer" do - it "should be instance of watchr script" do - expect(subject.observer).to be_instance_of(::Watchr::Script) - end - end - - describe "#watch" do - it "should pass the args to the observer" do - expect(subject.observer).to receive(:watch).with('lib') - subject.watch(:lib) - end - end - - describe "#watch_dir" do - it "should pass the pattern to the observer" do - expect(subject.observer).to receive(:watch).with("^spec/*/(.*).rb") - subject.watch_dir(:spec) - end - - it "should pass the pattern and the extension to the observer" do - expect(subject.observer).to receive(:watch).with("^spec/*/(.*).py") - subject.watch_dir(:spec, :py) - end - end - - describe "#start" do - it "should initialize an watchr controller passing the #observer" do - handler = double - controller = controller - expect(::Watchr.handler).to receive(:new).and_return(handler) - expect(::Watchr::Controller).to receive(:new).with(subject.observer, handler).and_return(controller) - expect(controller).to receive(:run).and_return(:running) - expect(subject.start).to equal :running - end - end - end - end -end diff --git a/spec/infinity_test/strategy/rbenv_spec.rb b/spec/infinity_test/strategy/rbenv_spec.rb index b6d0f54..d1b850d 100644 --- a/spec/infinity_test/strategy/rbenv_spec.rb +++ b/spec/infinity_test/strategy/rbenv_spec.rb @@ -3,19 +3,49 @@ module InfinityTest module Strategy describe Rbenv do - subject { Rbenv.new(Core::Base) } + let(:base) { BaseFixture.new(test_framework: :rspec) } + let(:continuous_test_server) { Core::ContinuousTestServer.new(base) } + subject { Rbenv.new(continuous_test_server) } + it_should_behave_like 'a infinity test strategy' describe ".run?" do let(:rbenv_dir) { File.expand_path('~/.rbenv') } - it "should return true if the user had the rbenv installed" do - expect(File).to receive(:exist?).with(rbenv_dir).and_return(true) - Rbenv.run? + + context "when rubies are specified" do + before do + allow(Core::Base).to receive(:rubies).and_return(['2.7.0', '3.0.0']) + end + + it "returns true if the user has rbenv installed" do + expect(File).to receive(:exist?).with(rbenv_dir).and_return(true) + expect(Rbenv).to be_run + end + + it "returns false if the user does not have rbenv installed" do + expect(File).to receive(:exist?).with(rbenv_dir).and_return(false) + expect(Rbenv).not_to be_run + end + end + + context "when no rubies are specified" do + before do + allow(Core::Base).to receive(:rubies).and_return([]) + end + + it "returns false even if rbenv is installed" do + expect(Rbenv).not_to be_run + end + end + end + + describe '#run!' do + before do + allow(Core::Base).to receive(:rubies).and_return(['2.7.0', '3.0.0']) end - it "should return false if the user don't had the rbenv installed" do - expect(File).to receive(:exist?).with(rbenv_dir).and_return(false) - Rbenv.run? + it 'returns the command for multiple ruby versions' do + expect(subject.run!).to eq 'RBENV_VERSION=2.7.0 ruby -S rspec spec && RBENV_VERSION=3.0.0 ruby -S rspec spec' end end end diff --git a/spec/infinity_test/strategy/ruby_default_spec.rb b/spec/infinity_test/strategy/ruby_default_spec.rb index da287e7..f134e72 100644 --- a/spec/infinity_test/strategy/ruby_default_spec.rb +++ b/spec/infinity_test/strategy/ruby_default_spec.rb @@ -9,13 +9,13 @@ module Strategy it_should_behave_like 'a infinity test strategy' describe ".run?" do - it "should return true when don't pass any ruby versions to run tests" do - Core::Base.stub(:rubies).and_return([]) + it "returns true when no ruby versions are passed to run tests" do + allow(Core::Base).to receive(:rubies).and_return([]) expect(RubyDefault).to be_run end - it "should return false when pass some ruby version to run tests" do - Core::Base.stub(:rubies).and_return(['ree', 'jruby']) + it "returns false when some ruby version is passed to run tests" do + allow(Core::Base).to receive(:rubies).and_return(['ree', 'jruby']) expect(RubyDefault).not_to be_run end end @@ -29,4 +29,4 @@ module Strategy end end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/strategy/rvm_spec.rb b/spec/infinity_test/strategy/rvm_spec.rb index 9b27e13..3000cce 100644 --- a/spec/infinity_test/strategy/rvm_spec.rb +++ b/spec/infinity_test/strategy/rvm_spec.rb @@ -3,27 +3,67 @@ module InfinityTest module Strategy describe Rvm do - subject { Rvm.new(Core::Base) } + let(:base) { BaseFixture.new(test_framework: :rspec) } + let(:continuous_test_server) { Core::ContinuousTestServer.new(base) } + subject { Rvm.new(continuous_test_server) } + it_should_behave_like 'a infinity test strategy' describe ".run?" do - it "should return true if the user had the RVM installed in users home" do - expect(Rvm).to receive(:installed_users_home?).and_return(true) - expect(Rvm).to be_run + context "when rubies are specified" do + before do + allow(Core::Base).to receive(:rubies).and_return(['2.7.0', '3.0.0']) + end + + it "returns true if the user has RVM installed in users home" do + expect(Rvm).to receive(:installed_users_home?).and_return(true) + expect(Rvm).to be_run + end + + it "returns true if the user has RVM installed system wide" do + expect(Rvm).to receive(:installed_users_home?).and_return(false) + expect(Rvm).to receive(:installed_system_wide?).and_return(true) + expect(Rvm).to be_run + end + + it "returns false if the user does not have RVM installed" do + expect(Rvm).to receive(:installed_users_home?).and_return(false) + expect(Rvm).to receive(:installed_system_wide?).and_return(false) + expect(Rvm).not_to be_run + end end - it "should return true if the user had the RVM installed in system wid" do - expect(Rvm).to receive(:installed_users_home?).and_return(false) - expect(Rvm).to receive(:installed_system_wide?).and_return(true) - expect(Rvm).to be_run + context "when no rubies are specified" do + before do + allow(Core::Base).to receive(:rubies).and_return([]) + end + + it "returns false even if RVM is installed" do + expect(Rvm).not_to be_run + end end + end + + describe '#run!' do + before do + allow(Core::Base).to receive(:rubies).and_return(['2.7.0', '3.0.0']) + allow(Core::Base).to receive(:gemset).and_return(nil) + end + + it 'returns the command for multiple ruby versions' do + expect(subject.run!).to eq 'rvm 2.7.0 do ruby -S rspec spec && rvm 3.0.0 do ruby -S rspec spec' + end + + context 'with gemset' do + before do + allow(Core::Base).to receive(:gemset).and_return('infinity_test') + end - it "should return false if the user don't had the RVM installed in users home" do - expect(Rvm).to receive(:installed_users_home?).and_return(false) - expect(Rvm).to receive(:installed_system_wide?).and_return(false) - expect(Rvm).not_to be_run + it 'includes gemset in the command' do + expect(subject.run!).to eq 'rvm 2.7.0@infinity_test do ruby -S rspec spec && rvm 3.0.0@infinity_test do ruby -S rspec spec' + end end end end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/test_framework/bacon_spec.rb b/spec/infinity_test/test_framework/bacon_spec.rb deleted file mode 100644 index 1be05d2..0000000 --- a/spec/infinity_test/test_framework/bacon_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'spec_helper' - -module InfinityTest - module TestFramework - describe Bacon do - it_should_behave_like 'a infinity test test framework' - end - end -end \ No newline at end of file diff --git a/spec/infinity_test/test_framework/rspec_spec.rb b/spec/infinity_test/test_framework/rspec_spec.rb index dd5ba9f..875f041 100644 --- a/spec/infinity_test/test_framework/rspec_spec.rb +++ b/spec/infinity_test/test_framework/rspec_spec.rb @@ -6,7 +6,7 @@ module TestFramework it_should_behave_like 'a infinity test test framework' describe "#test_dir" do - it "should return spec as test dir" do + it "returns spec as test dir" do expect(subject.test_dir).to eq 'spec' end end @@ -27,13 +27,13 @@ module TestFramework end describe "#test_helper_file" do - it "should be the spec helper" do + it "is the spec helper" do expect(subject.test_helper_file).to eq 'spec/spec_helper.rb' end end describe "#binary" do - it "should return rspec as binary" do + it "returns rspec as binary" do expect(subject.binary).to eq 'rspec' end end @@ -116,4 +116,4 @@ module TestFramework end end end -end \ No newline at end of file +end diff --git a/spec/infinity_test/test_framework/test_unit_spec.rb b/spec/infinity_test/test_framework/test_unit_spec.rb index b255b25..c4a73b2 100644 --- a/spec/infinity_test/test_framework/test_unit_spec.rb +++ b/spec/infinity_test/test_framework/test_unit_spec.rb @@ -6,20 +6,188 @@ module TestFramework it_should_behave_like 'a infinity test test framework' describe "#test_dir" do - it "should return spec as test dir" do + it "returns test as test dir" do expect(subject.test_dir).to eq 'test' end + + it "can be set to a custom directory" do + subject.test_dir = 'custom_test' + expect(subject.test_dir).to eq 'custom_test' + end + end + + describe '#test_files' do + context 'when is empty' do + it 'returns the test dir' do + expect(subject.test_files).to eq 'test' + end + end + + context 'when assign the test files' do + it 'returns the assigned value' do + subject.test_files = 'test/models/user_test.rb' + expect(subject.test_files).to eq 'test/models/user_test.rb' + end + end end describe "#test_helper_file" do - it "should be the spec helper" do + it "is the test helper" do expect(subject.test_helper_file).to eq 'test/test_helper.rb' end end describe '#binary' do - it 'should see the binary with the strategy instance' + it 'returns ruby as binary' do + expect(subject.binary).to eq 'ruby' + end + end + + describe '#test_message' do + subject(:test_unit) { TestUnit.new } + + it 'returns the final test results' do + test_unit.test_message = "Finished in 0.001s, 1000.0 runs/s.\n10 runs, 15 assertions, 0 failures, 0 errors, 0 skips\n" + expect(test_unit.test_message).to eq '10 runs, 15 assertions, 0 failures, 0 errors, 0 skips' + end + end + + describe '#success?' do + subject(:test_unit) { TestUnit.new } + + context 'when test message has ZERO failures, ZERO errors, and ZERO skips' do + before do + test_unit.test_message = "10 runs, 15 assertions, 0 failures, 0 errors, 0 skips" + end + + it 'returns true' do + expect(test_unit).to be_success + end + end + + context 'when test message has ONE failure' do + before do + test_unit.test_message = "10 runs, 15 assertions, 1 failures, 0 errors, 0 skips" + end + + it 'returns false' do + expect(test_unit).to_not be_success + end + end + + context 'when test message has ONE error' do + before do + test_unit.test_message = "10 runs, 15 assertions, 0 failures, 1 errors, 0 skips" + end + + it 'returns false' do + expect(test_unit).to_not be_success + end + end + + context 'when test message has ONE skip' do + before do + test_unit.test_message = "10 runs, 15 assertions, 0 failures, 0 errors, 1 skips" + end + + it 'returns false' do + expect(test_unit).to_not be_success + end + end + end + + describe '#failure?' do + subject(:test_unit) { TestUnit.new } + + context 'when test message has ONE failure' do + before do + test_unit.test_message = "10 runs, 15 assertions, 1 failures, 0 errors, 0 skips" + end + + it 'returns true' do + expect(test_unit).to be_failure + end + end + + context 'when test message has ONE error' do + before do + test_unit.test_message = "10 runs, 15 assertions, 0 failures, 1 errors, 0 skips" + end + + it 'returns true' do + expect(test_unit).to be_failure + end + end + + context 'when test message has ZERO failures and ZERO errors' do + before do + test_unit.test_message = "10 runs, 15 assertions, 0 failures, 0 errors, 0 skips" + end + + it 'returns false' do + expect(test_unit).to_not be_failure + end + end + end + + describe '#pending?' do + subject(:test_unit) { TestUnit.new } + + context 'when test message has ONE skip' do + before do + test_unit.test_message = "10 runs, 15 assertions, 0 failures, 0 errors, 1 skips" + end + + it 'returns true' do + expect(test_unit).to be_pending + end + end + + context 'when test message has ZERO skips' do + before do + test_unit.test_message = "10 runs, 15 assertions, 0 failures, 0 errors, 0 skips" + end + + it 'returns false' do + expect(test_unit).to_not be_pending + end + end + end + + describe '.run?' do + context 'when test directory exists with test files' do + before do + allow(File).to receive(:exist?).with('test').and_return(true) + allow(File).to receive(:exist?).with('test/test_helper.rb').and_return(true) + end + + it 'returns true' do + expect(TestUnit.run?).to be true + end + end + + context 'when test directory exists with *_test.rb files' do + before do + allow(File).to receive(:exist?).with('test').and_return(true) + allow(File).to receive(:exist?).with('test/test_helper.rb').and_return(false) + allow(Dir).to receive(:[]).with('test/**/*_test.rb').and_return(['test/user_test.rb']) + end + + it 'returns true' do + expect(TestUnit.run?).to be true + end + end + + context 'when test directory does not exist' do + before do + allow(File).to receive(:exist?).with('test').and_return(false) + end + + it 'returns false' do + expect(TestUnit.run?).to be false + end + end end end end -end \ No newline at end of file +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index eb6a0fd..4616ddd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -34,3 +34,14 @@ def initialize(options={}) @test_framework = options[:test_framework] end end + +# Replacement for deprecated silence_stream from ActiveSupport +def silence_stream(stream) + old_stream = stream.dup + stream.reopen(File::NULL) + stream.sync = true + yield +ensure + stream.reopen(old_stream) + old_stream.close +end