|
| 1 | +# Unit Test Performance Analysis |
| 2 | + |
| 3 | +This document explains why unit tests in cloud_controller_ng take a long time to load and proposes solutions. |
| 4 | + |
| 5 | +## The Problem |
| 6 | + |
| 7 | +Running a single unit test like `bundle exec rspec spec/unit/actions/app_create_spec.rb` takes approximately 20 seconds to load and only ~1.7 seconds to actually run. The load time dominates total test time. |
| 8 | + |
| 9 | +## Root Causes |
| 10 | + |
| 11 | +### 1. Eager Loading of the Entire Application (~1400 Ruby files) |
| 12 | + |
| 13 | +The `spec_helper.rb` requires `cloud_controller.rb` which triggers a cascade of requires: |
| 14 | + |
| 15 | +- `cloud_controller.rb` loads ~120 files directly from `lib/` |
| 16 | +- `cloud_controller/controllers.rb` loads all 117 controller files via `Dir[]` |
| 17 | +- `models.rb` loads 162 model files |
| 18 | +- `services.rb` loads service-related code |
| 19 | +- All 105 support files in `spec/support/` are loaded via `Dir[]` |
| 20 | + |
| 21 | +This eager loading happens on every test run, even for a single spec file. |
| 22 | + |
| 23 | +### 2. No Bootsnap Caching |
| 24 | + |
| 25 | +[Bootsnap](https://github.com/shopify/bootsnap) is a library that caches `require` calls (both the file path resolution and the compiled instruction sequences). It can reduce boot time by 50-70% for large Ruby applications. |
| 26 | + |
| 27 | +This project does not use bootsnap. |
| 28 | + |
| 29 | +### 3. Spring Not Properly Configured |
| 30 | + |
| 31 | +Spring and spring-commands-rspec are in the Gemfile, but Spring is not configured: |
| 32 | + |
| 33 | +- No `config/spring.rb` file exists to specify what to watch |
| 34 | +- Spring doesn't know which directories to preload |
| 35 | +- Without configuration, Spring may not preload the application effectively |
| 36 | + |
| 37 | +The bin/rspec attempts to use Spring, but without proper configuration, it may not be helping. |
| 38 | + |
| 39 | +### 4. Spork is Outdated and Ineffective |
| 40 | + |
| 41 | +The spec_helper.rb contains code to support Spork, but: |
| 42 | + |
| 43 | +- Spork is loaded from an old git ref and is no longer maintained |
| 44 | +- The implementation checks if spork is running via `ps | grep spork` which adds overhead |
| 45 | +- Spork has been superseded by Spring |
| 46 | + |
| 47 | +### 5. Database Setup on Every Run |
| 48 | + |
| 49 | +`SpecBootstrap.init` is called on every test run with: |
| 50 | + |
| 51 | +- `recreate_test_tables: true` (default) - recreates database tables |
| 52 | +- `do_schema_migration: true` (default) - runs migration checks |
| 53 | + |
| 54 | +These database operations add to startup time. |
| 55 | + |
| 56 | +## Solutions |
| 57 | + |
| 58 | +### Solution 1: Add Bootsnap (Recommended - Low Risk) |
| 59 | + |
| 60 | +Bootsnap can be added with minimal changes and provides significant speedup. |
| 61 | + |
| 62 | +**Implementation:** |
| 63 | + |
| 64 | +1. Add bootsnap to Gemfile: |
| 65 | + |
| 66 | +```ruby |
| 67 | +group :development, :test do |
| 68 | + gem 'bootsnap', require: false |
| 69 | +end |
| 70 | +``` |
| 71 | + |
| 72 | +2. Add to `config/boot.rb` before other requires: |
| 73 | + |
| 74 | +```ruby |
| 75 | +require 'bootsnap/setup' if ENV.fetch('DISABLE_BOOTSNAP', nil).nil? |
| 76 | +``` |
| 77 | + |
| 78 | +**Expected benefit:** 30-50% reduction in boot time with no risk to test behavior. |
| 79 | + |
| 80 | +### Solution 2: Properly Configure Spring (Recommended - Medium Risk) |
| 81 | + |
| 82 | +Create a proper Spring configuration so the preloader works effectively. |
| 83 | + |
| 84 | +**Implementation:** |
| 85 | + |
| 86 | +1. Create `config/spring.rb`: |
| 87 | + |
| 88 | +```ruby |
| 89 | +Spring.application_root = File.expand_path('..', __dir__) |
| 90 | + |
| 91 | +Spring.watch( |
| 92 | + '.ruby-version', |
| 93 | + 'Gemfile', |
| 94 | + 'Gemfile.lock' |
| 95 | +) |
| 96 | + |
| 97 | +# Watch the lib directory for changes |
| 98 | +%w[lib app spec/support].each do |path| |
| 99 | + Spring.watch path |
| 100 | +end |
| 101 | +``` |
| 102 | + |
| 103 | +2. Regenerate binstubs: |
| 104 | + |
| 105 | +```bash |
| 106 | +bundle exec spring binstub rspec |
| 107 | +``` |
| 108 | + |
| 109 | +3. Verify Spring is running: |
| 110 | + |
| 111 | +```bash |
| 112 | +bin/spring status |
| 113 | +``` |
| 114 | + |
| 115 | +**Expected benefit:** After first run, subsequent runs should complete in 1-3 seconds. |
| 116 | + |
| 117 | +### Solution 3: Remove Spork (Cleanup - Low Risk) |
| 118 | + |
| 119 | +Remove the outdated Spork code from spec_helper.rb to simplify the codebase. |
| 120 | + |
| 121 | +**Implementation:** |
| 122 | + |
| 123 | +Remove lines 5-13 and 225-239 from spec_helper.rb that deal with Spork, keeping only the `init_block.call` and `each_run_block.call` lines. |
| 124 | + |
| 125 | +### Solution 4: Skip Database Migration Checks |
| 126 | + |
| 127 | +Use the `NO_DB_MIGRATION=true` environment variable: |
| 128 | + |
| 129 | +```bash |
| 130 | +NO_DB_MIGRATION=true bundle exec rspec spec/unit/actions/app_create_spec.rb |
| 131 | +``` |
| 132 | + |
| 133 | +This skips migration checks when the schema is known to be up-to-date. |
| 134 | + |
| 135 | +**Expected benefit:** Saves 1-2 seconds per run. |
| 136 | + |
| 137 | +## Recommended Approach |
| 138 | + |
| 139 | +1. **First**: Add bootsnap (lowest risk, immediate benefit) |
| 140 | +2. **Second**: Configure Spring properly (requires testing, but greatest benefit for iterative development) |
| 141 | +3. **Third**: Remove Spork code (cleanup) |
| 142 | +4. **Optional**: Use NO_DB_MIGRATION when appropriate |
| 143 | + |
| 144 | +## Verification |
| 145 | + |
| 146 | +After implementing changes, measure improvement with: |
| 147 | + |
| 148 | +```bash |
| 149 | +time bundle exec rspec spec/unit/actions/app_create_spec.rb --format progress |
| 150 | +``` |
| 151 | + |
| 152 | +Compare the total time before and after each change. |
0 commit comments