├── .github └── workflows │ ├── changelog.yml │ ├── gempush.yml │ ├── rspec.yml │ └── standard.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── config └── locales │ └── pig_ci │ └── en.yml ├── lib ├── pig_ci.rb └── pig_ci │ ├── configuration.rb │ ├── decorator.rb │ ├── decorator │ └── report_terminal_decorator.rb │ ├── metric.rb │ ├── metric │ ├── current.rb │ ├── historial │ │ └── change_percentage.rb │ └── historical.rb │ ├── profiler.rb │ ├── profiler │ ├── database_request.rb │ ├── memory.rb │ └── request_time.rb │ ├── profiler_engine.rb │ ├── profiler_engine │ └── rails.rb │ ├── report.rb │ ├── report │ ├── database_request.rb │ ├── memory.rb │ └── request_time.rb │ ├── summary.rb │ ├── summary │ ├── ci.rb │ ├── html.rb │ └── terminal.rb │ ├── test_frameworks.rb │ ├── test_frameworks │ └── rspec.rb │ ├── version.rb │ └── views │ ├── index.erb │ └── report.erb ├── pig_ci.gemspec ├── public └── assets │ ├── application.css │ └── application.js └── spec ├── fixtures └── files │ ├── memory.json │ ├── profiler-two-entries.json │ ├── profiler.json │ └── profiler.txt ├── lib ├── pig_ci │ ├── configuration │ │ └── thresholds_spec.rb │ ├── decorator │ │ └── report_terminal_decorator_spec.rb │ ├── decorator_spec.rb │ ├── metic │ │ ├── current_spec.rb │ │ ├── historical │ │ │ └── change_percentage_spec.rb │ │ └── historical_spec.rb │ ├── profiler │ │ ├── memory_spec.rb │ │ ├── request_time_spec.rb │ │ └── sql_active_record_spec.rb │ ├── profiler_engine │ │ └── rails_spec.rb │ ├── profiler_engine_spec.rb │ ├── profiler_spec.rb │ ├── report │ │ └── memory_spec.rb │ ├── report_spec.rb │ └── summary │ │ ├── ci_spec.rb │ │ ├── html_spec.rb │ │ └── terminal_spec.rb └── pig_ci_spec.rb ├── spec_helper.rb └── support ├── api └── schemas │ └── pigci.com │ └── v1 │ └── reports │ └── schema.json ├── api_schema_matcher.rb └── pig_ci_rails_application.rb /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | # From: https://github.com/hopsoft/stimulus_reflex/blob/master/.github/workflows/changelog.yml 2 | name: Changelog 3 | 4 | on: 5 | workflow_dispatch: 6 | release: 7 | types: [created] 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 4 16 | if: "!contains(github.event.head_commit.message, '[nodoc]')" 17 | steps: 18 | - uses: actions/checkout@master 19 | - name: Set up Ruby 3.0 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: 3.0 23 | - uses: actions/cache@v2 24 | with: 25 | path: vendor/bundle 26 | key: ${{ runner.os }}-changelog-gem-${{ hashFiles('**/Gemfile.lock') }} 27 | restore-keys: | 28 | ${{ runner.os }}-changelog-gem- 29 | - name: Create local changes 30 | run: | 31 | gem install github_changelog_generator -v "1.15.2" 32 | github_changelog_generator -u ${{ github.repository_owner }} -p ${{ github.event.repository.name }} --token ${{ secrets.GITHUB_TOKEN }} --exclude-labels duplicate,question,invalid,wontfix,nodoc 33 | - name: Commit files 34 | run: | 35 | git config --local user.email "github-actions@example.com" 36 | git config --local user.name "GitHub Actions" 37 | git commit -am "[nodoc] Update Changelog" || echo "No changes to commit" 38 | - name: Push changes 39 | uses: ad-m/github-push-action@master 40 | with: 41 | github_token: ${{ secrets.GITHUB_TOKEN }} 42 | branch: ${{ github.ref }} 43 | -------------------------------------------------------------------------------- /.github/workflows/gempush.yml: -------------------------------------------------------------------------------- 1 | name: Build & Publish Ruby Gem 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | name: Build + Publish 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Publish to GitHub Packages 15 | uses: jstastny/publish-gem-to-github@master 16 | with: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | owner: MikeRogers0 19 | -------------------------------------------------------------------------------- /.github/workflows/rspec.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Testing 3 | 4 | on: 5 | push: 6 | pull_request: 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | ruby: [2.5, 2.6, 2.7] 14 | runs-on: ubuntu-latest 15 | name: Test against Ruby ${{ matrix.ruby }} 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: ${{ matrix.ruby }} 22 | bundler-cache: true 23 | - name: Run tests 24 | env: 25 | RAILS_ENV: test 26 | run: | 27 | bundle exec rake spec 28 | -------------------------------------------------------------------------------- /.github/workflows/standard.yml: -------------------------------------------------------------------------------- 1 | name: Standard 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - master 7 | jobs: 8 | standard: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Ruby 2.7 14 | uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: 2.7 17 | - name: Cache gems 18 | uses: actions/cache@v2 19 | with: 20 | path: vendor/bundle 21 | key: ${{ runner.os }}-standardrb 22 | - name: Install gems 23 | run: | 24 | bundle config path vendor/bundle 25 | gem install standardrb 26 | - name: Run Standard 27 | run: standardrb 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | 11 | *.gem 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v1.1.0](https://github.com/PigCI/pig-ci-rails/tree/v1.1.0) (2021-03-28) 4 | 5 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v1.0.1...v1.1.0) 6 | 7 | **Fixed bugs:** 8 | 9 | - Only eager loading app in Rails \>5.0 [\#38](https://github.com/PigCI/pig-ci-rails/pull/38) ([MikeRogers0](https://github.com/MikeRogers0)) 10 | 11 | **Merged pull requests:** 12 | 13 | - Adding End Of Life notice [\#68](https://github.com/PigCI/pig-ci-rails/pull/68) ([MikeRogers0](https://github.com/MikeRogers0)) 14 | - Updating .github/workflows to be simpler [\#67](https://github.com/PigCI/pig-ci-rails/pull/67) ([MikeRogers0](https://github.com/MikeRogers0)) 15 | - Using pigci.mikerogers.io over pigci.com [\#66](https://github.com/PigCI/pig-ci-rails/pull/66) ([MikeRogers0](https://github.com/MikeRogers0)) 16 | - Updating changelog generator to push to master [\#60](https://github.com/PigCI/pig-ci-rails/pull/60) ([MikeRogers0](https://github.com/MikeRogers0)) 17 | - Update webmock requirement from ~\> 3.9.3 to ~\> 3.11.0 [\#59](https://github.com/PigCI/pig-ci-rails/pull/59) ([dependabot[bot]](https://github.com/apps/dependabot)) 18 | - Upating Rubocop configuration [\#58](https://github.com/PigCI/pig-ci-rails/pull/58) ([MikeRogers0](https://github.com/MikeRogers0)) 19 | - Making GitHub Actions versions less specific [\#57](https://github.com/PigCI/pig-ci-rails/pull/57) ([MikeRogers0](https://github.com/MikeRogers0)) 20 | - Update simplecov requirement from ~\> 0.17.0 to ~\> 0.20.0 [\#56](https://github.com/PigCI/pig-ci-rails/pull/56) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 21 | - Update rspec requirement from ~\> 3.9.0 to ~\> 3.10.0 [\#53](https://github.com/PigCI/pig-ci-rails/pull/53) ([dependabot[bot]](https://github.com/apps/dependabot)) 22 | - Update terminal-table requirement from ~\> 1.8.0 to \>= 1.8, \< 2.1 [\#52](https://github.com/PigCI/pig-ci-rails/pull/52) ([dependabot[bot]](https://github.com/apps/dependabot)) 23 | - Revamping Github Actions [\#49](https://github.com/PigCI/pig-ci-rails/pull/49) ([MikeRogers0](https://github.com/MikeRogers0)) 24 | - Update actions/cache requirement to v2.1.2 [\#48](https://github.com/PigCI/pig-ci-rails/pull/48) ([dependabot[bot]](https://github.com/apps/dependabot)) 25 | - Update webmock requirement from ~\> 3.8.0 to ~\> 3.9.3 [\#47](https://github.com/PigCI/pig-ci-rails/pull/47) ([dependabot[bot]](https://github.com/apps/dependabot)) 26 | - Adding Dependabot.yml [\#45](https://github.com/PigCI/pig-ci-rails/pull/45) ([MikeRogers0](https://github.com/MikeRogers0)) 27 | - Ditching Docker Setup [\#44](https://github.com/PigCI/pig-ci-rails/pull/44) ([MikeRogers0](https://github.com/MikeRogers0)) 28 | - Adding funding\_uri metadata to gemspec [\#43](https://github.com/PigCI/pig-ci-rails/pull/43) ([MikeRogers0](https://github.com/MikeRogers0)) 29 | - Updating badges to reference GitHub Actions [\#42](https://github.com/PigCI/pig-ci-rails/pull/42) ([MikeRogers0](https://github.com/MikeRogers0)) 30 | - Removing Aptfile from repo [\#41](https://github.com/PigCI/pig-ci-rails/pull/41) ([MikeRogers0](https://github.com/MikeRogers0)) 31 | - Limiting GitHub Actions to just Rubocop & Rspec [\#40](https://github.com/PigCI/pig-ci-rails/pull/40) ([MikeRogers0](https://github.com/MikeRogers0)) 32 | - Updating docker setup to be a little slimmer [\#39](https://github.com/PigCI/pig-ci-rails/pull/39) ([MikeRogers0](https://github.com/MikeRogers0)) 33 | - Adding github sponsorship links [\#37](https://github.com/PigCI/pig-ci-rails/pull/37) ([MikeRogers0](https://github.com/MikeRogers0)) 34 | 35 | ## [v1.0.1](https://github.com/PigCI/pig-ci-rails/tree/v1.0.1) (2020-05-11) 36 | 37 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v1.0.0...v1.0.1) 38 | 39 | **Merged pull requests:** 40 | 41 | - Fixing spelling in the gemspec [\#36](https://github.com/PigCI/pig-ci-rails/pull/36) ([MikeRogers0](https://github.com/MikeRogers0)) 42 | - Adding autogenerated changelog [\#35](https://github.com/PigCI/pig-ci-rails/pull/35) ([MikeRogers0](https://github.com/MikeRogers0)) 43 | - Fixing missing logo in output summary [\#34](https://github.com/PigCI/pig-ci-rails/pull/34) ([MikeRogers0](https://github.com/MikeRogers0)) 44 | - Add option to ignore cached SQL queries [\#33](https://github.com/PigCI/pig-ci-rails/pull/33) ([MikeRogers0](https://github.com/MikeRogers0)) 45 | - Skip tracking on specific tests via RSpec metadata [\#32](https://github.com/PigCI/pig-ci-rails/pull/32) ([MikeRogers0](https://github.com/MikeRogers0)) 46 | - Adding in Docker [\#31](https://github.com/PigCI/pig-ci-rails/pull/31) ([MikeRogers0](https://github.com/MikeRogers0)) 47 | - Publishing Package to PigCI account [\#28](https://github.com/PigCI/pig-ci-rails/pull/28) ([MikeRogers0](https://github.com/MikeRogers0)) 48 | 49 | ## [v1.0.0](https://github.com/PigCI/pig-ci-rails/tree/v1.0.0) (2020-04-07) 50 | 51 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.2.2...v1.0.0) 52 | 53 | **Merged pull requests:** 54 | 55 | - Removing PigCI.com integration [\#27](https://github.com/PigCI/pig-ci-rails/pull/27) ([MikeRogers0](https://github.com/MikeRogers0)) 56 | 57 | ## [v0.2.2](https://github.com/PigCI/pig-ci-rails/tree/v0.2.2) (2020-03-23) 58 | 59 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.2.1...v0.2.2) 60 | 61 | **Merged pull requests:** 62 | 63 | - Fixing grammar on post\_install\_message [\#25](https://github.com/PigCI/pig-ci-rails/pull/25) ([MikeRogers0](https://github.com/MikeRogers0)) 64 | 65 | ## [v0.2.1](https://github.com/PigCI/pig-ci-rails/tree/v0.2.1) (2020-03-19) 66 | 67 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.2.0...v0.2.1) 68 | 69 | **Merged pull requests:** 70 | 71 | - Stop GitHub actions not to run on commit [\#24](https://github.com/PigCI/pig-ci-rails/pull/24) ([MikeRogers0](https://github.com/MikeRogers0)) 72 | - Handling JSON response for API key being incorrect with correct error [\#23](https://github.com/PigCI/pig-ci-rails/pull/23) ([MikeRogers0](https://github.com/MikeRogers0)) 73 | 74 | ## [v0.2.0](https://github.com/PigCI/pig-ci-rails/tree/v0.2.0) (2020-03-18) 75 | 76 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.5...v0.2.0) 77 | 78 | **Merged pull requests:** 79 | 80 | - Using GitHubs example for gempush.yml [\#22](https://github.com/PigCI/pig-ci-rails/pull/22) ([MikeRogers0](https://github.com/MikeRogers0)) 81 | - Replace references for limit to be threshold [\#21](https://github.com/PigCI/pig-ci-rails/pull/21) ([MikeRogers0](https://github.com/MikeRogers0)) 82 | - Setting up for 0.2.0 release [\#20](https://github.com/PigCI/pig-ci-rails/pull/20) ([MikeRogers0](https://github.com/MikeRogers0)) 83 | - Adding in action for when I tag a new version [\#19](https://github.com/PigCI/pig-ci-rails/pull/19) ([MikeRogers0](https://github.com/MikeRogers0)) 84 | - Adding config.thresholds, this allows standalone usage without pigci.com [\#18](https://github.com/PigCI/pig-ci-rails/pull/18) ([MikeRogers0](https://github.com/MikeRogers0)) 85 | - Update webmock requirement from ~\> 3.7.0 to ~\> 3.8.0 [\#17](https://github.com/PigCI/pig-ci-rails/pull/17) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 86 | - Update rspec requirement from ~\> 3.8.0 to ~\> 3.9.0 [\#16](https://github.com/PigCI/pig-ci-rails/pull/16) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 87 | - Updating TravisCI to test latest ruby versions [\#15](https://github.com/PigCI/pig-ci-rails/pull/15) ([MikeRogers0](https://github.com/MikeRogers0)) 88 | - Update rake requirement from ~\> 12.3 to ~\> 13.0 [\#14](https://github.com/PigCI/pig-ci-rails/pull/14) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 89 | 90 | ## [v0.1.5](https://github.com/PigCI/pig-ci-rails/tree/v0.1.5) (2019-09-23) 91 | 92 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.4...v0.1.5) 93 | 94 | **Merged pull requests:** 95 | 96 | - Widen i18n gem version requirement [\#12](https://github.com/PigCI/pig-ci-rails/pull/12) ([mileszim](https://github.com/mileszim)) 97 | 98 | ## [v0.1.4](https://github.com/PigCI/pig-ci-rails/tree/v0.1.4) (2019-09-10) 99 | 100 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.3...v0.1.4) 101 | 102 | **Implemented enhancements:** 103 | 104 | - Precompile assets before tests run [\#11](https://github.com/PigCI/pig-ci-rails/pull/11) ([MikeRogers0](https://github.com/MikeRogers0)) 105 | 106 | **Merged pull requests:** 107 | 108 | - Create FUNDING.yml [\#10](https://github.com/PigCI/pig-ci-rails/pull/10) ([MikeRogers0](https://github.com/MikeRogers0)) 109 | 110 | ## [v0.1.3](https://github.com/PigCI/pig-ci-rails/tree/v0.1.3) (2019-08-28) 111 | 112 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.2...v0.1.3) 113 | 114 | **Fixed bugs:** 115 | 116 | - Gracefully handling SockerError when submitting reports while offline [\#7](https://github.com/PigCI/pig-ci-rails/pull/7) ([MikeRogers0](https://github.com/MikeRogers0)) 117 | 118 | **Merged pull requests:** 119 | 120 | - Adding Dependabot Status badge to readme [\#9](https://github.com/PigCI/pig-ci-rails/pull/9) ([MikeRogers0](https://github.com/MikeRogers0)) 121 | - Fixing duplicate word typo in readme [\#8](https://github.com/PigCI/pig-ci-rails/pull/8) ([MikeRogers0](https://github.com/MikeRogers0)) 122 | - Update webmock requirement from ~\> 3.6.0 to ~\> 3.7.0 [\#6](https://github.com/PigCI/pig-ci-rails/pull/6) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 123 | 124 | ## [v0.1.2](https://github.com/PigCI/pig-ci-rails/tree/v0.1.2) (2019-08-03) 125 | 126 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.1...v0.1.2) 127 | 128 | **Implemented enhancements:** 129 | 130 | - Add options to control eager loading of rails app [\#3](https://github.com/PigCI/pig-ci-rails/pull/3) ([MikeRogers0](https://github.com/MikeRogers0)) 131 | 132 | **Fixed bugs:** 133 | 134 | - Fixing issue where timezone would becomes the machines on app warmup [\#5](https://github.com/PigCI/pig-ci-rails/pull/5) ([MikeRogers0](https://github.com/MikeRogers0)) 135 | 136 | **Merged pull requests:** 137 | 138 | - Fixing up some codacy issues [\#4](https://github.com/PigCI/pig-ci-rails/pull/4) ([MikeRogers0](https://github.com/MikeRogers0)) 139 | 140 | ## [v0.1.1](https://github.com/PigCI/pig-ci-rails/tree/v0.1.1) (2019-07-31) 141 | 142 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.0...v0.1.1) 143 | 144 | **Merged pull requests:** 145 | 146 | - Adding fallback for if PigCI is have API issues [\#2](https://github.com/PigCI/pig-ci-rails/pull/2) ([MikeRogers0](https://github.com/MikeRogers0)) 147 | - Adding test coverage with codeclimate [\#1](https://github.com/PigCI/pig-ci-rails/pull/1) ([MikeRogers0](https://github.com/MikeRogers0)) 148 | 149 | ## [v0.1.0](https://github.com/PigCI/pig-ci-rails/tree/v0.1.0) (2019-07-30) 150 | 151 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/26699470a370a1ff576f820a0742be5f332561be...v0.1.0) 152 | 153 | 154 | 155 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 156 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at me@mikerogers.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in pig_ci.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mike Rogers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | PigCI 3 |

4 | 5 |

6 | Monitor your Ruby Applications metrics (Memory, SQL Requests & Request Time) as part of your test suite. If your app exceeds an acceptable threshold it'll fail the test suite. 7 |

8 | 9 |

10 | 11 | Gem Version 12 | 13 | 14 | RSpec 15 | 16 | 17 | Linters 18 | 19 |

20 | 21 | 22 | ## Deprecation notice 23 | 24 | This gem is not longer actively maintained, I suggest using theses alternatives instead: 25 | 26 | - [TestProf](https://github.com/test-prof/test-prof) 27 | - [rack-mini-profiler](https://github.com/MiniProfiler/rack-mini-profiler) 28 | - [RSpec::Benchmark](https://github.com/piotrmurach/rspec-benchmark) 29 | 30 | ## Sample Output 31 | 32 | ![Sample Output of PigCI in TravisCI](https://user-images.githubusercontent.com/325384/78711087-545b6400-790e-11ea-96b7-bb75c119914a.png) 33 | 34 | ## Installation 35 | 36 | Add this line to your application's Gemfile: 37 | 38 | ```ruby 39 | group :test do 40 | gem 'pig-ci-rails' 41 | end 42 | ``` 43 | 44 | And then execute: 45 | 46 | ```bash 47 | $ bundle 48 | ``` 49 | 50 | Or install it yourself as: 51 | 52 | ```bash 53 | $ gem install pig-ci-rails 54 | ``` 55 | 56 | ## Usage 57 | 58 | ### On it's own 59 | 60 | ```ruby 61 | # In spec/rails_helper.rb 62 | require 'pig_ci' 63 | PigCI.start 64 | ``` 65 | 66 | ### Configuring thresholds 67 | 68 | Configuring the thresholds will allow your test suite to fail in CI. You will need to configure the thresholds depending on your application. 69 | 70 | ```ruby 71 | # In spec/rails_helper.rb 72 | require 'pig_ci' 73 | PigCI.start do |config| 74 | # Maximum memory in megabytes 75 | config.thresholds.memory = 350 76 | 77 | # Maximum time per a HTTP request 78 | config.thresholds.request_time = 250 79 | 80 | # Maximum database calls per a request 81 | config.thresholds.database_request = 35 82 | end if RSpec.configuration.files_to_run.count > 1 83 | ``` 84 | 85 | ### Configuring other options 86 | 87 | This gems was setup to be configured by passing a block to the `PigCI.start` method, e.g: 88 | 89 | ```ruby 90 | # In spec/rails_helper.rb 91 | require 'pig_ci' 92 | PigCI.start do |config| 93 | config.option = 'new_value' 94 | 95 | # E.g. disable terminal summary output 96 | config.generate_terminal_summary = false 97 | 98 | # Rails caches repeated SQL queries, you might want to omit these from your report. 99 | config.ignore_cached_queries = true 100 | end # if RSpec.configuration.files_to_run.count > 1 101 | ``` 102 | 103 | You can see the full configuration options [lib/pig_ci.rb](https://github.com/PigCI/pig-ci-rails/blob/master/lib/pig_ci.rb#L21). 104 | 105 | ### Skipping individual tests 106 | 107 | If you have a scenario where you'd like PigCI to not log a specific test, you can add the RSpec metadata `pig_ci: true`. For example: 108 | 109 | ```ruby 110 | RSpec.describe "Comments", type: :request do 111 | # This test block will be not be tracked. 112 | describe "GET #index", pig_ci: false do 113 | it do 114 | get comments_path 115 | expect(response).to be_successful 116 | end 117 | end 118 | end 119 | ``` 120 | 121 | ### Framework support 122 | 123 | Currently this gem only supports Ruby on Rails tested via RSpec. 124 | 125 | ### Metric notes 126 | 127 | Minor fluctuations in memory usage and request time are to be expected and are nothing to worry about. Though any large spike is a signal of something worth investigating. 128 | 129 | #### Memory 130 | 131 | By default, this gem will tell Rails to eager load your application on startup. This aims to help identify leaks, over just pure bulk. 132 | 133 | You can disable this functionality by setting your configuration to be: 134 | 135 | ```ruby 136 | require 'pig_ci' 137 | PigCI.start do |config| 138 | config.during_setup_eager_load_application = false 139 | end 140 | ``` 141 | 142 | #### Request Time 143 | 144 | Often the first request test will be slow, as rails is loading a full environment & rendering assets. To mitigate this issue, this gem will make a blank request to your application before your test suite starts & compiling assets. 145 | 146 | You can disable this functionality by setting your configuration to be: 147 | 148 | ```ruby 149 | require 'pig_ci' 150 | PigCI.start do |config| 151 | config.during_setup_make_blank_application_request = false 152 | config.during_setup_precompile_assets = false 153 | end 154 | ``` 155 | 156 | ## Authors 157 | 158 | * This gem was made by [@MikeRogers0](https://github.com/MikeRogers0). 159 | * It was originally inspired by [oink](https://github.com/noahd1/oink), after it was used to [monitor acceptance tests](https://mikerogers.io/2015/03/28/monitor-rails-memory-usage-in-integration-tests.html) and it spotted a memory leak. It seemed like something that would be useful to have as part of CI. 160 | * The HTML output was inspired by [simplecov](https://github.com/colszowka/simplecov). 161 | 162 | ## Development 163 | 164 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 165 | 166 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 167 | 168 | ## Contributing 169 | 170 | Bug reports and pull requests are welcome on GitHub at [PigCI/pig-ci-rails](https://github.com/PigCI/pig-ci-rails). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 171 | 172 | ## Code of Conduct 173 | 174 | Everyone interacting in the PigCI project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/PigCI/pig-ci-rails/blob/master/CODE_OF_CONDUCT.md). 175 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | begin 3 | require "rspec/core/rake_task" 4 | 5 | RSpec::Core::RakeTask.new(:spec) 6 | 7 | task default: :spec 8 | rescue LoadError 9 | # no rspec available 10 | end 11 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "pig_ci" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /config/locales/pig_ci/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | pig_ci: 3 | summary: 4 | ci_start: 'PigCI Thresholds Summary:' 5 | ci_failure: 'PigCI: This commit has exceeded the thresholds defined in PigCI.thresholds' 6 | saved_successfully: PigCI report generated to %{output_directory} 7 | title: PigCI Results 8 | view_historic_reports: 'Historic Reports' 9 | footer_html: | 10 |

Generated by PigCI.

11 |

Support PigCI by buying me a coffee.

12 | 13 | report: 14 | memory: 15 | name: Peak memory per a request 16 | attributes: 17 | key: Key 18 | max: Max (MB) 19 | min: Min (MB) 20 | mean: Mean (MB) 21 | number_of_requests: Requests 22 | max_change_percentage: "% Change" 23 | 24 | request_time: 25 | name: Request Time 26 | attributes: 27 | key: Key 28 | max: Max (ms) 29 | min: Min (ms) 30 | mean: Mean (ms) 31 | number_of_requests: Requests 32 | max_change_percentage: "% Change" 33 | 34 | database_request: 35 | name: Queries to the database 36 | attributes: 37 | key: Key 38 | max: Max 39 | min: Min 40 | mean: Mean 41 | number_of_requests: Requests 42 | max_change_percentage: "% Change" 43 | -------------------------------------------------------------------------------- /lib/pig_ci.rb: -------------------------------------------------------------------------------- 1 | require "active_support" 2 | require "active_support/core_ext/string/inflections" 3 | require "rake" 4 | 5 | require "pig_ci/version" 6 | require "pig_ci/configuration" 7 | require "pig_ci/decorator" 8 | require "pig_ci/summary" 9 | require "pig_ci/profiler_engine" 10 | require "pig_ci/profiler" 11 | require "pig_ci/metric" 12 | require "pig_ci/report" 13 | require "pig_ci/test_frameworks" 14 | 15 | module PigCI 16 | extend self 17 | 18 | attr_accessor :pid 19 | 20 | attr_writer :enabled 21 | def enabled? 22 | @enabled.nil? ? true : @enabled 23 | end 24 | 25 | # Rails caches repeated queries within the same request. You can not count 26 | # any cached queries if you'd like. 27 | attr_writer :ignore_cached_queries 28 | def ignore_cached_queries? 29 | @ignore_cached_queries.nil? ? false : @ignore_cached_queries 30 | end 31 | 32 | attr_writer :tmp_directory 33 | def tmp_directory 34 | @tmp_directory || Pathname.new(Dir.getwd).join("tmp", "pig-ci") 35 | end 36 | 37 | attr_writer :output_directory 38 | def output_directory 39 | @output_directory || Pathname.new(Dir.getwd).join("pig-ci") 40 | end 41 | 42 | attr_accessor :generate_terminal_summary 43 | def generate_terminal_summary? 44 | @generate_terminal_summary.nil? || @generate_terminal_summary 45 | end 46 | 47 | attr_accessor :generate_html_summary 48 | def generate_html_summary? 49 | @generate_html_summary.nil? || @generate_html_summary 50 | end 51 | 52 | attr_writer :max_change_percentage_precision 53 | def max_change_percentage_precision 54 | @max_change_percentage_precision || 1 55 | end 56 | 57 | attr_writer :report_memory_precision 58 | def report_memory_precision 59 | @report_memory_precision || 2 60 | end 61 | 62 | attr_writer :during_setup_eager_load_application 63 | def during_setup_eager_load_application? 64 | @during_setup_eager_load_application.nil? || @during_setup_eager_load_application 65 | end 66 | 67 | attr_writer :during_setup_make_blank_application_request 68 | def during_setup_make_blank_application_request? 69 | @during_setup_make_blank_application_request.nil? || @during_setup_make_blank_application_request 70 | end 71 | 72 | attr_writer :during_setup_precompile_assets 73 | def during_setup_precompile_assets? 74 | @during_setup_precompile_assets.nil? || @during_setup_precompile_assets 75 | end 76 | 77 | attr_writer :terminal_report_row_limit 78 | def terminal_report_row_limit 79 | @terminal_report_row_limit || -1 80 | end 81 | 82 | # PigCI.report_row_sort_by = Proc.new { |d| d[:max_change_percentage] * -1 } 83 | attr_writer :report_row_sort_by 84 | def report_row_sort_by(data) 85 | (@report_row_sort_by || proc { |d| d[:max].to_i * -1 }).call(data) 86 | end 87 | 88 | attr_writer :historical_data_run_limit 89 | def historical_data_run_limit 90 | @historical_data_run_limit ||= 10 91 | end 92 | 93 | attr_writer :run_timestamp 94 | def run_timestamp 95 | @run_timestamp ||= Time.now.to_i.to_s 96 | end 97 | 98 | attr_writer :profiler_engine 99 | def profiler_engine 100 | @profiler_engine ||= PigCI::ProfilerEngine::Rails.new 101 | end 102 | 103 | attr_writer :commit_sha1 104 | def commit_sha1 105 | @commit_sha1 || ENV["CI_COMMIT_ID"] || ENV["CIRCLE_SHA1"] || ENV["TRAVIS_COMMIT"] || `git rev-parse HEAD`.strip 106 | end 107 | 108 | attr_writer :head_branch 109 | def head_branch 110 | @head_branch || ENV["CI_BRANCH"] || ENV["CIRCLE_BRANCH"] || ENV["TRAVIS_BRANCH"] || `git rev-parse --abbrev-ref HEAD`.strip 111 | end 112 | 113 | # Throw deprecation notice for setting API 114 | def api_key=(value) 115 | puts "DEPRECATED: PigCI.com API has been retired, you no longer need to set config.api_key in your spec/rails_helper.rb file." 116 | end 117 | 118 | attr_writer :locale 119 | def locale 120 | @locale || :en 121 | end 122 | 123 | def thresholds=(values) 124 | @thresholds = PigCI::Configuration::Thresholds.new(values) 125 | end 126 | 127 | def thresholds 128 | @thresholds ||= PigCI::Configuration::Thresholds.new 129 | end 130 | 131 | module_function 132 | 133 | def start(&block) 134 | self.pid = Process.pid 135 | PigCI::TestFrameworks::Rspec.configure! if defined?(::RSpec) 136 | 137 | block&.call(self) 138 | 139 | # Add our translations 140 | load_i18ns! 141 | 142 | # Make sure our directories exist 143 | Dir.mkdir(tmp_directory) unless File.exist?(tmp_directory) 144 | Dir.mkdir(output_directory) unless File.exist?(output_directory) 145 | 146 | # Purge any previous logs and attach some listeners 147 | profiler_engine.setup! 148 | end 149 | 150 | def load_i18ns! 151 | I18n.available_locales << PigCI.locale 152 | I18n.load_path += Dir["#{File.expand_path("../config/locales/pig_ci", __dir__)}/*.{rb,yml}"] 153 | end 154 | 155 | def run_exit_tasks! 156 | return if PigCI.pid != Process.pid || !PigCI.profiler_engine.request_captured? 157 | 158 | # Save all the reports as JSON 159 | profiler_engine.profilers.each(&:save!) 160 | 161 | # Print the report summary to Terminal 162 | PigCI::Summary::Terminal.new(reports: profiler_engine.reports).print! if PigCI.generate_terminal_summary? 163 | 164 | # Save the report summary to the project root. 165 | PigCI::Summary::HTML.new(reports: profiler_engine.reports).save! if PigCI.generate_html_summary? 166 | 167 | # Make sure CI fails when metrics are over thresholds. 168 | PigCI::Summary::CI.new(reports: profiler_engine.reports).call! 169 | end 170 | end 171 | 172 | at_exit do 173 | PigCI.run_exit_tasks! 174 | end 175 | -------------------------------------------------------------------------------- /lib/pig_ci/configuration.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Configuration 2 | Thresholds = Struct.new(:memory, :request_time, :database_request) { 3 | def initialize(memory: 350, request_time: 250, database_request: 35) 4 | super(memory, request_time, database_request) 5 | end 6 | } 7 | end 8 | -------------------------------------------------------------------------------- /lib/pig_ci/decorator.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Decorator 2 | attr_accessor :object 3 | 4 | def initialize(object) 5 | @object = object 6 | end 7 | end 8 | 9 | require "pig_ci/decorator/report_terminal_decorator" 10 | -------------------------------------------------------------------------------- /lib/pig_ci/decorator/report_terminal_decorator.rb: -------------------------------------------------------------------------------- 1 | require "colorized_string" 2 | 3 | class PigCI::Decorator::ReportTerminalDecorator < PigCI::Decorator 4 | %i[key max min mean number_of_requests].each do |field| 5 | define_method(field) do 6 | @object[field] 7 | end 8 | end 9 | 10 | def max_change_percentage 11 | if @object[:max_change_percentage].start_with?("-") 12 | ColorizedString[@object[:max_change_percentage]].colorize(:green) 13 | elsif @object[:max_change_percentage].start_with?("0.0") 14 | @object[:max_change_percentage] 15 | else 16 | ColorizedString[@object[:max_change_percentage]].colorize(:red) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pig_ci/metric.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Metric; end 2 | 3 | require "pig_ci/metric/current" 4 | require "pig_ci/metric/historical" 5 | -------------------------------------------------------------------------------- /lib/pig_ci/metric/current.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Metric::Current 2 | def initialize(log_file:) 3 | @log_file = log_file 4 | end 5 | 6 | def to_h 7 | @to_h = {} 8 | 9 | File.foreach(@log_file) do |f| 10 | key, value = f.strip.split("|") 11 | value = value.to_i 12 | 13 | @to_h[key] ||= { 14 | key: key, 15 | max: value, 16 | min: value, 17 | mean: 0, 18 | total: 0, 19 | number_of_requests: 0 20 | } 21 | @to_h[key][:max] = value if value > @to_h[key][:max] 22 | @to_h[key][:min] = value if value < @to_h[key][:min] 23 | @to_h[key][:total] += value 24 | @to_h[key][:number_of_requests] += 1 25 | @to_h[key][:mean] = @to_h[key][:total] / @to_h[key][:number_of_requests] 26 | end 27 | 28 | @to_h.collect { |_k, d| d } 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pig_ci/metric/historial/change_percentage.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Metric::Historical::ChangePercentage 2 | def initialize(previous_data:, data:) 3 | @previous_data = previous_data 4 | @data = data 5 | @timestamp = @data.keys.first 6 | @profiler = @data[@timestamp].keys.first 7 | end 8 | 9 | def updated_data 10 | @data[@timestamp][@profiler].collect do |data| 11 | previous_run_data = previous_run_data_for_key(data[:key]) || data 12 | 13 | data[:max_change_percentage] = (((BigDecimal(data[:max]) - BigDecimal(previous_run_data[:max])) / BigDecimal(previous_run_data[:max])) * 100).round(PigCI.max_change_percentage_precision) 14 | data[:max_change_percentage] = BigDecimal("0") if data[:max_change_percentage].to_s == "NaN" || data[:max_change_percentage] == BigDecimal("-0.0") 15 | data[:max_change_percentage] = data[:max_change_percentage].to_f 16 | 17 | data 18 | end 19 | 20 | @data 21 | end 22 | 23 | private 24 | 25 | def previous_run_data_for_key(key) 26 | previous_data_keys.each do |previous_run_key| 27 | @previous_data[previous_run_key][@profiler.to_sym].each do |raw_previous_run_data| 28 | return raw_previous_run_data if raw_previous_run_data[:key] == key 29 | end 30 | end 31 | nil 32 | end 33 | 34 | def previous_data_keys 35 | @previous_data_keys ||= @previous_data.keys.sort.reverse 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/pig_ci/metric/historical.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Metric::Historical 2 | def initialize(historical_log_file:) 3 | @historical_log_file = historical_log_file 4 | end 5 | 6 | def to_h 7 | @to_h ||= read_historical_log_file.sort_by { |timestamp, _data| timestamp.to_s.to_i * -1 }.to_h 8 | end 9 | 10 | # In future this might honour some limit. 11 | def append!(timestamp:, metric:, data:) 12 | to_h 13 | @to_h[timestamp] ||= {} 14 | @to_h[timestamp][metric] = data 15 | remove_old_historical_data! 16 | save! 17 | end 18 | 19 | def add_change_percentage_and_append!(timestamp:, metric:, data:) 20 | max_change_percentage_data = {} 21 | max_change_percentage_data[timestamp] = {} 22 | max_change_percentage_data[timestamp][metric] = data 23 | 24 | data = PigCI::Metric::Historical::ChangePercentage.new(previous_data: to_h, data: max_change_percentage_data).updated_data 25 | append!(timestamp: timestamp, metric: metric, data: data[timestamp][metric]) 26 | end 27 | 28 | private 29 | 30 | def remove_old_historical_data! 31 | new_historical_data = @to_h 32 | .sort_by { |timestamp, _data| timestamp.to_s.to_i * -1 }[0..(PigCI.historical_data_run_limit - 1)] 33 | .to_h 34 | .sort_by { |timestamp, _data| timestamp.to_s.to_i * -1 }.to_h 35 | @to_h = new_historical_data 36 | end 37 | 38 | def read_historical_log_file 39 | if File.exist?(@historical_log_file) 40 | JSON.parse(File.open(@historical_log_file, "r").read, symbolize_names: true) 41 | else 42 | {} 43 | end 44 | end 45 | 46 | def save! 47 | File.write(@historical_log_file, @to_h.to_json) 48 | @to_h = nil 49 | end 50 | end 51 | 52 | require "pig_ci/metric/historial/change_percentage" 53 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Profiler 2 | attr_accessor :log_value, :log_file, :historical_log_file, :i18n_key 3 | 4 | def initialize(i18n_key: nil, log_file: nil, historical_log_file: nil) 5 | @i18n_key = i18n_key || self.class.name.underscore.split("/").last 6 | @log_file = log_file || PigCI.tmp_directory.join("#{@i18n_key}.txt") 7 | @historical_log_file = historical_log_file || PigCI.tmp_directory.join("#{@i18n_key}.json") 8 | @log_value = 0 9 | end 10 | 11 | def setup! 12 | File.open(log_file, "w") { |file| file.truncate(0) } 13 | end 14 | 15 | def reset! 16 | @log_value = 0 17 | end 18 | 19 | def log_request!(request_key) 20 | File.open(log_file, "a+") do |f| 21 | f.puts([request_key, log_value].join("|")) 22 | end 23 | end 24 | 25 | def save! 26 | historical_data = PigCI::Metric::Historical.new(historical_log_file: @historical_log_file) 27 | historical_data.add_change_percentage_and_append!( 28 | timestamp: PigCI.run_timestamp, 29 | metric: i18n_key, 30 | data: PigCI::Metric::Current.new(log_file: log_file).to_h 31 | ) 32 | end 33 | 34 | def increment!(*) 35 | raise NotImplementedError 36 | end 37 | end 38 | 39 | require "pig_ci/profiler/memory" 40 | require "pig_ci/profiler/request_time" 41 | require "pig_ci/profiler/database_request" 42 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler/database_request.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Profiler::DatabaseRequest < PigCI::Profiler 2 | def increment!(by: 1) 3 | @log_value += by 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler/memory.rb: -------------------------------------------------------------------------------- 1 | require "get_process_mem" 2 | 3 | class PigCI::Profiler::Memory < PigCI::Profiler 4 | def reset! 5 | GC.disable 6 | end 7 | 8 | def log_request!(request_key) 9 | GC.enable 10 | super 11 | end 12 | 13 | def log_value 14 | ::GetProcessMem.new.bytes 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler/request_time.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Profiler::RequestTime < PigCI::Profiler 2 | attr_accessor :start_time, :end_time 3 | 4 | def reset! 5 | super 6 | @start_time = Time.now.utc 7 | end 8 | 9 | def log_request!(request_key) 10 | @end_time = Time.now.utc 11 | super 12 | end 13 | 14 | def log_value 15 | (@end_time - @start_time) * 1000.0 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler_engine.rb: -------------------------------------------------------------------------------- 1 | class PigCI::ProfilerEngine 2 | attr_accessor :request_key, :profilers, :reports, :request_captured 3 | 4 | def initialize(profilers: nil, reports: nil) 5 | @profilers = profilers || [] 6 | @reports = reports || [] 7 | @request_captured = false 8 | end 9 | 10 | def request_key? 11 | !@request_key.nil? && @request_key != "" 12 | end 13 | 14 | def request_captured? 15 | @request_captured 16 | end 17 | 18 | def request_captured! 19 | @request_captured = true 20 | end 21 | 22 | def setup! 23 | Dir.mkdir(PigCI.tmp_directory) unless File.exist?(PigCI.tmp_directory) 24 | 25 | yield if block_given? 26 | 27 | profilers.collect(&:setup!) 28 | 29 | # Attach listeners to the rails events. 30 | attach_listeners! 31 | end 32 | 33 | private 34 | 35 | def attach_listeners! 36 | raise NotImplementedError 37 | end 38 | end 39 | 40 | require "pig_ci/profiler_engine/rails" 41 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler_engine/rails.rb: -------------------------------------------------------------------------------- 1 | # This subscribes to the ActiveSupport::Notifications and passes it onto our profilers. 2 | class PigCI::ProfilerEngine::Rails < ::PigCI::ProfilerEngine 3 | def initialize(profilers: nil, reports: nil) 4 | @profilers = profilers || [ 5 | PigCI::Profiler::Memory.new, 6 | PigCI::Profiler::RequestTime.new, 7 | PigCI::Profiler::DatabaseRequest.new 8 | ] 9 | @reports = reports || [ 10 | PigCI::Report::Memory.new, 11 | PigCI::Report::RequestTime.new, 12 | PigCI::Report::DatabaseRequest.new 13 | ] 14 | @request_captured = false 15 | end 16 | 17 | def request_key_from_payload!(payload) 18 | @request_key = "#{payload[:method]} #{payload[:controller]}##{payload[:action]}{format:#{payload[:format]}}" 19 | end 20 | 21 | def setup! 22 | super do 23 | precompile_assets! if PigCI.during_setup_precompile_assets? 24 | eager_load_rails! if PigCI.during_setup_eager_load_application? 25 | make_blank_application_request! if PigCI.during_setup_make_blank_application_request? 26 | end 27 | end 28 | 29 | private 30 | 31 | def precompile_assets! 32 | # From: https://github.com/rails/sprockets-rails/blob/e9ca63edb6e658cdfcf8a35670c525b369c2ccca/test/test_railtie.rb#L7-L13 33 | ::Rails.application.load_tasks 34 | ::Rake.application["assets:precompile"].execute 35 | end 36 | 37 | def eager_load_rails! 38 | # None of these methods will work pre-rails 5. 39 | return unless ::Rails.version.to_f >= 5.0 40 | 41 | # Eager load rails to give more accurate memory levels. 42 | ::Rails.application.eager_load! 43 | ::Rails.application.routes.eager_load! 44 | ::Rails::Engine.subclasses.map(&:instance).each(&:eager_load!) 45 | ::ActiveRecord::Base.descendants 46 | end 47 | 48 | def make_blank_application_request! 49 | # Make a call to the root path to load up as much of rails as possible 50 | # Done within a timezone block as it affects the timezone. 51 | Time.use_zone("UTC") do 52 | ::Rails.application.call(::Rack::MockRequest.env_for("/")) 53 | end 54 | end 55 | 56 | def attach_listeners! 57 | ::ActiveSupport::Notifications.subscribe "start_processing.action_controller" do |_name, _started, _finished, _unique_id, payload| 58 | request_key_from_payload!(payload) 59 | 60 | profilers.each(&:reset!) 61 | end 62 | 63 | ::ActiveSupport::Notifications.subscribe "sql.active_record" do |_name, _started, _finished, _unique_id, payload| 64 | if request_key? && PigCI.enabled? && (!PigCI.ignore_cached_queries? || (PigCI.ignore_cached_queries? && !payload[:cached])) 65 | profilers.select { |profiler| profiler.instance_of?(PigCI::Profiler::DatabaseRequest) }.each(&:increment!) 66 | end 67 | end 68 | 69 | ::ActiveSupport::Notifications.subscribe "process_action.action_controller" do |_name, _started, _finished, _unique_id, _payload| 70 | if PigCI.enabled? 71 | profilers.each do |profiler| 72 | profiler.log_request!(request_key) 73 | end 74 | 75 | request_captured! 76 | end 77 | self.request_key = nil 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/pig_ci/report.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Report 2 | attr_accessor :historical_log_file, :i18n_key 3 | 4 | def initialize(historical_log_file: nil, i18n_key: nil, timestamp: nil) 5 | @i18n_key = i18n_key || self.class.name.underscore.split("/").last 6 | @historical_log_file = historical_log_file || PigCI.tmp_directory.join("#{@i18n_key}.json") 7 | @timestamp = timestamp || PigCI.run_timestamp 8 | end 9 | 10 | def headings 11 | column_keys.collect { |key| I18n.t(".attributes.#{key}", scope: i18n_scope, locale: PigCI.locale) } 12 | end 13 | 14 | def i18n_name 15 | I18n.t(".name", scope: i18n_scope, locale: PigCI.locale) 16 | end 17 | 18 | def max_for(timestamp) 19 | sorted_and_formatted_data_for(timestamp).collect { |row| row[:max] }.max 20 | end 21 | 22 | def threshold 23 | PigCI.thresholds.dig(@i18n_key.to_sym) 24 | end 25 | 26 | def over_threshold_for?(timestamp) 27 | return false unless threshold.present? && max_for(timestamp).present? 28 | 29 | max_for(timestamp) > threshold 30 | end 31 | 32 | def sorted_and_formatted_data_for(timestamp) 33 | data_for(timestamp)[@i18n_key.to_sym].sort_by { |data| 34 | PigCI.report_row_sort_by(data) 35 | }.collect do |data| 36 | self.class.format_row(data) 37 | end 38 | end 39 | 40 | def to_payload_for(timestamp) 41 | { 42 | profiler: @i18n_key.to_sym, 43 | data: data_for(timestamp)[@i18n_key.to_sym] 44 | } 45 | end 46 | 47 | def historical_data 48 | @historical_data ||= PigCI::Metric::Historical.new(historical_log_file: @historical_log_file).to_h 49 | end 50 | 51 | def timestamps 52 | historical_data.keys 53 | end 54 | 55 | def column_keys 56 | %i[key max min mean number_of_requests max_change_percentage] 57 | end 58 | 59 | def i18n_scope 60 | @i18n_scope ||= "pig_ci.report.#{i18n_key}" 61 | end 62 | 63 | def self.format_row(row) 64 | row = row.dup 65 | row[:max_change_percentage] = "#{row[:max_change_percentage]}%" 66 | row 67 | end 68 | 69 | private 70 | 71 | def data_for(timestamp) 72 | historical_data[timestamp.to_sym] 73 | end 74 | end 75 | 76 | require "pig_ci/report/memory" 77 | require "pig_ci/report/request_time" 78 | require "pig_ci/report/database_request" 79 | -------------------------------------------------------------------------------- /lib/pig_ci/report/database_request.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Report::DatabaseRequest < PigCI::Report 2 | end 3 | -------------------------------------------------------------------------------- /lib/pig_ci/report/memory.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Report::Memory < PigCI::Report 2 | def self.format_row(_unformatted_row) 3 | row = super 4 | row[:max] = (row[:max] / bytes_in_a_megabyte).round(PigCI.report_memory_precision) 5 | row[:min] = (row[:min] / bytes_in_a_megabyte).round(PigCI.report_memory_precision) 6 | row[:mean] = (row[:mean] / bytes_in_a_megabyte).round(PigCI.report_memory_precision) 7 | row[:total] = (row[:total] / bytes_in_a_megabyte).round(PigCI.report_memory_precision) 8 | 9 | row 10 | end 11 | 12 | def self.bytes_in_a_megabyte 13 | @bytes_in_a_megabyte ||= BigDecimal("1_048_576") 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/pig_ci/report/request_time.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Report::RequestTime < PigCI::Report 2 | end 3 | -------------------------------------------------------------------------------- /lib/pig_ci/summary.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Summary; end 2 | 3 | require "pig_ci/summary/ci" 4 | require "pig_ci/summary/html" 5 | require "pig_ci/summary/terminal" 6 | -------------------------------------------------------------------------------- /lib/pig_ci/summary/ci.rb: -------------------------------------------------------------------------------- 1 | require "colorized_string" 2 | 3 | class PigCI::Summary::CI < PigCI::Summary 4 | def initialize(reports:) 5 | @reports = reports 6 | @timestamp = PigCI.run_timestamp 7 | end 8 | 9 | def call! 10 | puts "" 11 | puts I18n.t("pig_ci.summary.ci_start") 12 | 13 | over_threshold = false 14 | @reports.each do |report| 15 | print_report(report) 16 | over_threshold = true if report.over_threshold_for?(@timestamp) 17 | end 18 | 19 | fail_with_error! if over_threshold 20 | puts "" 21 | end 22 | 23 | private 24 | 25 | def fail_with_error! 26 | puts I18n.t("pig_ci.summary.ci_failure") 27 | Kernel.exit(2) 28 | end 29 | 30 | def print_report(report) 31 | max_and_threshold = [ 32 | report.max_for(@timestamp).to_s, 33 | "/", 34 | report.threshold 35 | ].join(" ") 36 | 37 | if report.over_threshold_for?(@timestamp) 38 | puts "#{report.i18n_name}: #{ColorizedString[max_and_threshold].colorize(:red)}\n" 39 | else 40 | puts "#{report.i18n_name}: #{ColorizedString[max_and_threshold].colorize(:green)}\n" 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/pig_ci/summary/html.rb: -------------------------------------------------------------------------------- 1 | # This will make human friendly HTML report for developers to compare runs locally. 2 | # It aims to save to the project root in /pig-ci. 3 | # It is inspired by https://github.com/colszowka/simplecov-html 4 | 5 | require "erb" 6 | 7 | class PigCI::Summary::HTML < PigCI::Summary 8 | def initialize(reports:) 9 | @reports = reports 10 | end 11 | 12 | def save! 13 | copy_assets! 14 | File.write(index_file_path, template("index").result(binding)) 15 | 16 | puts I18n.t("pig_ci.summary.saved_successfully", output_directory: PigCI.output_directory) 17 | end 18 | 19 | private 20 | 21 | def render_report(report) 22 | template("report").result(binding) 23 | end 24 | 25 | def timestamps 26 | @reports.first.timestamps 27 | end 28 | 29 | def template(name) 30 | ERB.new(File.read(File.join(File.dirname(__FILE__), "../views", "#{name}.erb"))) 31 | end 32 | 33 | def index_file_path 34 | PigCI.output_directory.join("index.html") 35 | end 36 | 37 | def copy_assets! 38 | Dir.mkdir(output_assets_directory) unless File.exist?(output_assets_directory) 39 | FileUtils.copy_entry(assets_directory, output_assets_directory) 40 | end 41 | 42 | def output_assets_directory 43 | PigCI.output_directory.join("assets") 44 | end 45 | 46 | def assets_directory 47 | File.join(File.dirname(__FILE__), "../../../public/assets") 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/pig_ci/summary/terminal.rb: -------------------------------------------------------------------------------- 1 | require "terminal-table" 2 | 3 | class PigCI::Summary::Terminal < PigCI::Summary 4 | def initialize(reports:) 5 | @reports = reports 6 | @timestamp = PigCI.run_timestamp 7 | end 8 | 9 | def print! 10 | @reports.each do |report| 11 | print_report(report) 12 | end 13 | end 14 | 15 | private 16 | 17 | def print_report(report) 18 | puts "#{report.i18n_name}:\n" 19 | 20 | table = ::Terminal::Table.new headings: report.headings do |t| 21 | report.sorted_and_formatted_data_for(@timestamp)[0..PigCI.terminal_report_row_limit] 22 | .collect { |data| PigCI::Decorator::ReportTerminalDecorator.new(data) } 23 | .each do |data| 24 | t << report.column_keys.collect { |key| data.send(key) } 25 | end 26 | end 27 | puts table 28 | puts "\n" 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pig_ci/test_frameworks.rb: -------------------------------------------------------------------------------- 1 | module PigCI::TestFrameworks; end 2 | 3 | require "pig_ci/test_frameworks/rspec" 4 | -------------------------------------------------------------------------------- /lib/pig_ci/test_frameworks/rspec.rb: -------------------------------------------------------------------------------- 1 | class PigCI::TestFrameworks::Rspec 2 | def self.configure! 3 | if defined?(::RSpec) 4 | ::RSpec.configure do |config| 5 | config.around(:each, pig_ci: false) do |example| 6 | @pig_ci_enabled = PigCI.enabled? 7 | PigCI.enabled = false 8 | example.run 9 | PigCI.enabled = @pig_ci_enabled 10 | end 11 | 12 | config.around(:each, pig_ci: true) do |example| 13 | @pig_ci_enabled = PigCI.enabled? 14 | PigCI.enabled = true 15 | example.run 16 | PigCI.enabled = @pig_ci_enabled 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/pig_ci/version.rb: -------------------------------------------------------------------------------- 1 | module PigCI 2 | VERSION = "1.1.0".freeze 3 | end 4 | -------------------------------------------------------------------------------- /lib/pig_ci/views/index.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PigCI Results 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 | 23 | PigCI Logo 24 | 25 |

<%= I18n.t('.pig_ci.summary.title') %>

26 |
27 |
28 | 43 |
44 |
45 |
46 | 47 |
48 | 55 | 56 |
57 |
58 | <% @reports.each_with_index do |report, index| %> 59 |
60 | <%= render_report(report) %> 61 |
62 | <% end %> 63 |
64 |
65 |
66 | 67 | 70 |
71 | 72 | 73 | -------------------------------------------------------------------------------- /lib/pig_ci/views/report.erb: -------------------------------------------------------------------------------- 1 |
2 |

<%= report.i18n_name %>

3 | 4 | <% report.historical_data.each_with_index do |(timestamp, historic_data), index| %> 5 |
6 |

<%= Time.at(timestamp.to_s.to_i) %>

7 | 8 |
9 | 10 | 11 | 12 | <% report.headings.each do |heading| %> 13 | 14 | <% end %> 15 | 16 | 17 | 18 | <% report.sorted_and_formatted_data_for(timestamp).each do |historic_row| %> 19 | 20 | <% report.column_keys.each do |column_key| %> 21 | 22 | <% end %> 23 | 24 | <% end %> 25 | 26 |
<%= heading %>
<%= historic_row[column_key] %>
27 |
28 |
29 | <% end %> 30 |
31 | -------------------------------------------------------------------------------- /pig_ci.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("lib", __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require "pig_ci/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "pig-ci-rails" 7 | spec.version = PigCI::VERSION 8 | spec.authors = ["Mike Rogers"] 9 | spec.email = ["me@mikerogers.io"] 10 | 11 | spec.summary = "Monitor your Ruby Applications metrics via your test suite." 12 | spec.description = "A gem for Ruby on Rails that will track key metrics (memory, request time & SQL Requests) for request & feature tests." 13 | spec.homepage = "https://github.com/PigCI/pig-ci-rails" 14 | spec.license = "MIT" 15 | 16 | spec.metadata = { 17 | "bug_tracker_uri" => "#{spec.homepage}/issues", 18 | "changelog_uri" => "#{spec.homepage}/blob/master/CHANGELOG.md", 19 | "documentation_uri" => spec.homepage, 20 | "homepage_uri" => spec.homepage, 21 | "source_code_uri" => spec.homepage, 22 | "funding_uri" => "https://www.buymeacoffee.com/MikeRogers0" 23 | } 24 | 25 | # Specify which files should be added to the gem when it is released. 26 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 27 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 28 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 29 | end 30 | spec.require_paths = ["lib"] 31 | spec.required_ruby_version = ">= 2.5" 32 | 33 | spec.add_dependency "activesupport", ">= 4.2" 34 | spec.add_dependency "colorize", ">= 0.8.1" 35 | spec.add_dependency "get_process_mem", "~> 0.2.3" 36 | spec.add_dependency "i18n", ">= 0.9", "< 2" 37 | spec.add_dependency "rails", ">= 4.2.0" 38 | spec.add_dependency "terminal-table", ">= 1.8", "< 2.1" 39 | 40 | spec.add_development_dependency "bundler", "~> 2.0" 41 | spec.add_development_dependency "rake", "~> 13.0" 42 | spec.add_development_dependency "webmock", "~> 3.11.0" 43 | 44 | spec.add_development_dependency "json-schema", "~> 2.8.1" 45 | spec.add_development_dependency "rspec", "~> 3.10.0" 46 | spec.add_development_dependency "simplecov", "~> 0.20.0" 47 | spec.add_development_dependency "yard", "~> 0.9.24" 48 | end 49 | -------------------------------------------------------------------------------- /public/assets/application.css: -------------------------------------------------------------------------------- 1 | .dropdown-item--historic-reports[aria-expanded="true"] { 2 | font-weight: bold; 3 | } 4 | -------------------------------------------------------------------------------- /public/assets/application.js: -------------------------------------------------------------------------------- 1 | // Any JS here. 2 | -------------------------------------------------------------------------------- /spec/fixtures/files/memory.json: -------------------------------------------------------------------------------- 1 | { 2 | "100": { 3 | "memory": [ 4 | { 5 | "key": "request-key", 6 | "max": 12, 7 | "min": 6, 8 | "mean": 9, 9 | "total": 18, 10 | "number_of_requests": 2, 11 | "max_change_percentage": 0 12 | }, 13 | { 14 | "key": "request-key-2", 15 | "max": 2, 16 | "min": 2, 17 | "mean": 2, 18 | "total": 2, 19 | "number_of_requests": 1, 20 | "max_change_percentage": 0 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spec/fixtures/files/profiler-two-entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "100": { 3 | "profiler": [ 4 | { 5 | "key": "request-key", 6 | "max": 12, 7 | "min": 6, 8 | "mean": 9, 9 | "total": 18, 10 | "number_of_requests": 2, 11 | "max_change_percentage": 0 12 | }, 13 | { 14 | "key": "request-key-2", 15 | "max": 2, 16 | "min": 2, 17 | "mean": 2, 18 | "total": 2, 19 | "number_of_requests": 1, 20 | "max_change_percentage": 0 21 | } 22 | ] 23 | }, 24 | "101": { 25 | "profiler": [ 26 | { 27 | "key": "request-key-3", 28 | "max": 12, 29 | "min": 6, 30 | "mean": 9, 31 | "total": 18, 32 | "number_of_requests": 2, 33 | "max_change_percentage": 0 34 | } 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spec/fixtures/files/profiler.json: -------------------------------------------------------------------------------- 1 | {"100":{"profiler":[{"key":"request-key","max":12,"min":6,"mean":9,"total":18,"number_of_requests":2, "max_change_percentage": 0.0 },{"key":"request-key-2","max":2,"min":2,"mean":2,"total":2,"number_of_requests":1, "max_change_percentage": 0.0}]}} 2 | -------------------------------------------------------------------------------- /spec/fixtures/files/profiler.txt: -------------------------------------------------------------------------------- 1 | request-key|12 2 | request-key|6 3 | request-key-2|2 4 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/configuration/thresholds_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Configuration::Thresholds do 4 | let(:instance_class) { described_class.new } 5 | 6 | describe "::new" do 7 | subject { described_class.new } 8 | 9 | it "has default values" do 10 | expect(subject.memory).to eq(350) 11 | expect(subject.request_time).to eq(250) 12 | expect(subject.database_request).to eq(35) 13 | end 14 | 15 | context "configured with a hash" do 16 | subject do 17 | described_class.new({ 18 | memory: 300, 19 | request_time: 200, 20 | database_request: 25 21 | }) 22 | end 23 | 24 | it do 25 | expect(subject.memory).to eq(300) 26 | expect(subject.request_time).to eq(200) 27 | expect(subject.database_request).to eq(25) 28 | end 29 | end 30 | end 31 | 32 | describe "#memory" do 33 | subject { instance_class.memory } 34 | it { is_expected.to eq(350) } 35 | end 36 | 37 | describe "#memory=" do 38 | subject { instance_class.memory = 200 } 39 | it { expect { subject }.to change(instance_class, :memory).from(350).to(200) } 40 | end 41 | 42 | describe "#dig" do 43 | subject { instance_class.dig(:memory) } 44 | it { is_expected.to eq(350) } 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/decorator/report_terminal_decorator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Decorator::ReportTerminalDecorator do 4 | let(:object) do 5 | { 6 | key: "request-key", 7 | min: 1, 8 | max: 1, 9 | mean: 2, 10 | number_of_requests: 1, 11 | max_change_percentage: "0.0%" 12 | } 13 | end 14 | let(:decorator) { PigCI::Decorator::ReportTerminalDecorator.new(object) } 15 | 16 | describe "#key" do 17 | subject { decorator.key } 18 | 19 | it { is_expected.to eq("request-key") } 20 | end 21 | 22 | describe "#max_change_percentage" do 23 | subject { decorator.max_change_percentage } 24 | 25 | it { is_expected.to eq("0.0%") } 26 | 27 | context "minus result" do 28 | let(:object) do 29 | { 30 | max_change_percentage: "-4.0%" 31 | } 32 | end 33 | 34 | it { is_expected.to eq("\e[0;32;49m-4.0%\e[0m") } 35 | end 36 | 37 | context "minus result" do 38 | let(:object) do 39 | { 40 | max_change_percentage: "4.0%" 41 | } 42 | end 43 | 44 | it { is_expected.to eq("\e[0;31;49m4.0%\e[0m") } 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/decorator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Decorator do 4 | let(:decorator) { PigCI::Decorator.new(object) } 5 | 6 | describe "#object" do 7 | let(:object) { double :object } 8 | subject { decorator.object } 9 | 10 | it { is_expected.to eq(object) } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/metic/current_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Metric::Current do 4 | let(:log_file) { File.join("spec", "fixtures", "files", "profiler.txt") } 5 | let(:metric_current) { PigCI::Metric::Current.new(log_file: log_file) } 6 | 7 | describe "#to_h" do 8 | subject { metric_current.to_h } 9 | 10 | let(:expected_output) do 11 | [ 12 | {key: "request-key", max: 12, mean: 9, min: 6, number_of_requests: 2, total: 18}, 13 | {key: "request-key-2", max: 2, mean: 2, min: 2, number_of_requests: 1, total: 2} 14 | ] 15 | end 16 | 17 | it { is_expected.to eq(expected_output) } 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/metic/historical/change_percentage_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Metric::Historical::ChangePercentage do 4 | let(:change_percentage) { PigCI::Metric::Historical::ChangePercentage.new(previous_data: previous_data, data: data) } 5 | 6 | let(:data) do 7 | {"101" => {profiler: [{key: "request-key", max: 12}]}} 8 | end 9 | 10 | describe "#updated_data" do 11 | subject { change_percentage.updated_data } 12 | 13 | context "no previous data" do 14 | let(:previous_data) { {} } 15 | let(:expected_response) do 16 | {"101" => {profiler: [{key: "request-key", max: 12, max_change_percentage: 0.0}]}} 17 | end 18 | 19 | it { is_expected.to eq(expected_response) } 20 | end 21 | 22 | context "previous for a different key" do 23 | let(:previous_data) do 24 | {"100" => {profiler: [{key: "request-key-2", max: 12}]}} 25 | end 26 | let(:expected_response) do 27 | {"101" => {profiler: [{key: "request-key", max: 12, max_change_percentage: 0.0}]}} 28 | end 29 | 30 | it { is_expected.to eq(expected_response) } 31 | end 32 | 33 | context "previous data has run from a while back that matches with higher value" do 34 | let(:previous_data) do 35 | { 36 | "99" => {profiler: [{key: "request-key", max: 24}]}, 37 | "100" => {profiler: [{key: "request-key-2", max: 12}]} 38 | } 39 | end 40 | let(:expected_response) do 41 | {"101" => {profiler: [{key: "request-key", max: 12, max_change_percentage: -50.0}]}} 42 | end 43 | 44 | it { is_expected.to eq(expected_response) } 45 | end 46 | 47 | context "data is lower previous value" do 48 | let(:previous_data) do 49 | {"100" => {profiler: [{key: "request-key", max: 24}]}} 50 | end 51 | let(:expected_response) do 52 | {"101" => {profiler: [{key: "request-key", max: 12, max_change_percentage: -50.0}]}} 53 | end 54 | 55 | it { is_expected.to eq(expected_response) } 56 | end 57 | 58 | context "data is higher previous value" do 59 | let(:previous_data) do 60 | {"100" => {profiler: [{key: "request-key", max: 6}]}} 61 | end 62 | let(:expected_response) do 63 | {"101" => {profiler: [{key: "request-key", max: 12, max_change_percentage: 100.0}]}} 64 | end 65 | 66 | it { is_expected.to eq(expected_response) } 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/metic/historical_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Metric::Historical do 4 | let(:historical_log_file) { File.join("spec", "fixtures", "files", "profiler.json") } 5 | let(:metric_historical) { PigCI::Metric::Historical.new(historical_log_file: historical_log_file) } 6 | 7 | describe "#to_h" do 8 | subject { metric_historical.to_h } 9 | 10 | let(:expected_response) do 11 | {"100": { 12 | profiler: [ 13 | {key: "request-key", max: 12, mean: 9, min: 6, number_of_requests: 2, total: 18, max_change_percentage: 0.0}, 14 | {key: "request-key-2", max: 2, mean: 2, min: 2, number_of_requests: 1, total: 2, max_change_percentage: 0.0} 15 | ] 16 | }} 17 | end 18 | it { is_expected.to eq(expected_response) } 19 | 20 | context "With two entries" do 21 | let(:historical_log_file) { File.join("spec", "fixtures", "files", "profiler-two-entries.json") } 22 | 23 | it "sorts with latest request first" do 24 | expect(subject.keys.first).to eq(:'101') 25 | end 26 | end 27 | end 28 | 29 | describe "#add_change_percentage_and_append!" do 30 | pending "It should limit the amount of data stored somehow" 31 | end 32 | 33 | describe "#append!" do 34 | let(:timestamp) { "102" } 35 | let(:metric) { "profiler" } 36 | let(:data) do 37 | [ 38 | {key: "request-key-3", max: 2, mean: 2, min: 2, number_of_requests: 1, total: 2, max_change_percentage: 0.0} 39 | ] 40 | end 41 | 42 | let(:new_data_as_json) do 43 | { 44 | "100": { 45 | profiler: [ 46 | {key: "request-key", max: 12, mean: 9, min: 6, number_of_requests: 2, total: 18, max_change_percentage: 0.0}, 47 | {key: "request-key-2", max: 2, mean: 2, min: 2, number_of_requests: 1, total: 2, max_change_percentage: 0.0} 48 | ] 49 | }, 50 | "102": { 51 | profiler: [ 52 | {key: "request-key-3", max: 2, mean: 2, min: 2, number_of_requests: 1, total: 2, max_change_percentage: 0.0} 53 | ] 54 | } 55 | }.to_json 56 | end 57 | 58 | subject { metric_historical.append!(timestamp: timestamp, metric: metric, data: data) } 59 | 60 | it "updates the hash, and saves the file to disk" do 61 | expect(File).to receive(:write).once do |file, new_data| 62 | expect(file).to eq(historical_log_file) 63 | expect(JSON.parse(new_data)).to eq(JSON.parse(new_data_as_json)) 64 | end 65 | 66 | subject 67 | end 68 | 69 | context "With a historical_data_run_limit of 0" do 70 | let(:historical_log_file) { File.join("spec", "fixtures", "files", "profiler-two-entries.json") } 71 | 72 | let(:new_data_as_json) do 73 | { 74 | "101": { 75 | profiler: [ 76 | {key: "request-key-3", max: 12, mean: 9, min: 6, number_of_requests: 2, total: 18, max_change_percentage: 0} 77 | ] 78 | }, 79 | "102": { 80 | profiler: [ 81 | {key: "request-key-3", max: 2, mean: 2, min: 2, number_of_requests: 1, total: 2, max_change_percentage: 0.0} 82 | ] 83 | } 84 | }.to_json 85 | end 86 | 87 | before { PigCI.historical_data_run_limit = 2 } 88 | after { PigCI.historical_data_run_limit = 10 } 89 | 90 | it "removes oldest record" do 91 | expect(File).to receive(:write).once do |file, new_data| 92 | expect(file).to eq(historical_log_file) 93 | expect(JSON.parse(new_data)).to eq(JSON.parse(new_data_as_json)) 94 | end 95 | 96 | subject 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/profiler/memory_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Profiler::Memory do 4 | let(:profiler) { PigCI::Profiler::Memory.new } 5 | 6 | describe "#reset!" do 7 | subject { profiler.reset! } 8 | 9 | it "disables garbage collection" do 10 | expect(GC).to receive(:disable) 11 | subject 12 | end 13 | end 14 | 15 | describe "#log_request!(request_key)" do 16 | subject { profiler.log_request!("request-key") } 17 | let(:get_process_mem) { double :get_process_mem, bytes: 55 } 18 | 19 | before { profiler.setup! } 20 | 21 | it "enables garbage collection, and saves memory to logs" do 22 | expect(GC).to receive(:enable) 23 | expect(GetProcessMem).to receive(:new).and_return(get_process_mem) 24 | 25 | expect { subject }.to change(profiler.log_file, :read).from("").to("request-key|55\n") 26 | end 27 | end 28 | 29 | describe "#i18n_key" do 30 | subject { profiler.i18n_key } 31 | 32 | it { is_expected.to eq("memory") } 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/profiler/request_time_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Profiler::RequestTime do 4 | let(:profiler) { PigCI::Profiler::RequestTime.new } 5 | 6 | describe "#reset!" do 7 | subject { profiler.reset! } 8 | 9 | it do 10 | profiler.start_time = nil 11 | expect { subject }.to change(profiler, :start_time).from(nil).to(Time) 12 | end 13 | end 14 | 15 | describe "#log_request!" do 16 | subject { profiler.log_request!("request-key") } 17 | before { profiler.setup! && profiler.reset! } 18 | 19 | it "sets end_time and saves the delta to the log file" do 20 | expect { subject }.to change(profiler, :end_time).from(nil).to(Time) 21 | .and change(profiler.log_file, :read).from("").to(/request-key/) 22 | end 23 | end 24 | 25 | describe "#log_value" do 26 | subject { profiler.log_value } 27 | 28 | before do 29 | profiler.start_time = Time.at(0) 30 | profiler.end_time = Time.at(10) 31 | end 32 | it { is_expected.to eq(10_000) } 33 | end 34 | 35 | describe "#i18n_key" do 36 | subject { profiler.i18n_key } 37 | 38 | it { is_expected.to eq("request_time") } 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/profiler/sql_active_record_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Profiler::DatabaseRequest do 4 | let(:profiler) { PigCI::Profiler::DatabaseRequest.new } 5 | 6 | describe "#increment!" do 7 | subject { profiler.increment! } 8 | 9 | it { expect { subject }.to change(profiler, :log_value).by(1) } 10 | 11 | context "with by argument" do 12 | subject { profiler.increment!(by: 2) } 13 | 14 | it { expect { subject }.to change(profiler, :log_value).by(2) } 15 | end 16 | end 17 | 18 | describe "#i18n_key" do 19 | subject { profiler.i18n_key } 20 | 21 | it { is_expected.to eq("database_request") } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/profiler_engine/rails_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::ProfilerEngine do 4 | let(:profiler_engine) { PigCI::ProfilerEngine::Rails.new } 5 | 6 | before do 7 | # Stub out the complex eager loading a little 8 | allow(Rails.application).to receive(:call) 9 | allow(Rake.application).to receive(:[]).and_return(double(:rake_task, execute: true)) 10 | end 11 | 12 | describe "#setup!" do 13 | subject { profiler_engine.setup! } 14 | 15 | it do 16 | expect(profiler_engine).to receive(:precompile_assets!) 17 | expect(profiler_engine).to receive(:eager_load_rails!) 18 | expect(profiler_engine).to receive(:make_blank_application_request!) 19 | subject 20 | end 21 | 22 | context "with PigCI.during_setup_eager_load_application set to false" do 23 | before { PigCI.during_setup_eager_load_application = false } 24 | after { PigCI.during_setup_eager_load_application = nil } 25 | it do 26 | expect(profiler_engine).to_not receive(:eager_load_rails!) 27 | subject 28 | end 29 | end 30 | 31 | context "with PigCI.during_setup_make_blank_application_request set to false" do 32 | before { PigCI.during_setup_make_blank_application_request = false } 33 | after { PigCI.during_setup_make_blank_application_request = nil } 34 | it do 35 | expect(profiler_engine).to_not receive(:make_blank_application_request!) 36 | subject 37 | end 38 | end 39 | 40 | context "with PigCI.during_setup_make_blank_application_request set to false" do 41 | before { PigCI.during_setup_precompile_assets = false } 42 | after { PigCI.during_setup_precompile_assets = nil } 43 | it do 44 | expect(profiler_engine).to_not receive(:precompile_assets!) 45 | subject 46 | end 47 | end 48 | end 49 | 50 | describe "#eager_load_rails!" do 51 | subject { profiler_engine.send(:eager_load_rails!) } 52 | 53 | it "Below Rails 5.2, it does not eager load the app" do 54 | expect(::Rails).to receive(:version).and_return("4.2.5") 55 | expect(::Rails.application).to_not receive(:eager_load!) 56 | subject 57 | end 58 | end 59 | 60 | describe "#make_blank_application_request!" do 61 | subject { profiler_engine.send(:make_blank_application_request!) } 62 | 63 | it "does not changes the current timezone" do 64 | expect { subject }.to_not change(Time, :zone) 65 | end 66 | end 67 | 68 | describe "#profilers" do 69 | subject { profiler_engine.profilers } 70 | it { expect(subject.count).to eq(3) } 71 | end 72 | 73 | describe "#reports" do 74 | subject { profiler_engine.reports } 75 | it { expect(subject.count).to eq(3) } 76 | end 77 | 78 | describe "::ActiveSupport::Notifications" do 79 | let(:payload) do 80 | { 81 | method: "GET", 82 | controller: "SampleController", 83 | action: "index", 84 | format: "html" 85 | } 86 | end 87 | before { profiler_engine.setup! } 88 | after do 89 | ActiveSupport::Notifications.unsubscribe("start_processing.action_controller") 90 | ActiveSupport::Notifications.unsubscribe("instantiation.active_record") 91 | ActiveSupport::Notifications.unsubscribe("sql.active_record") 92 | ActiveSupport::Notifications.unsubscribe("process_action.action_controller") 93 | end 94 | 95 | describe "start_processing.action_controller" do 96 | subject do 97 | ActiveSupport::Notifications.instrument("start_processing.action_controller", payload) {} 98 | end 99 | 100 | it do 101 | profiler_engine.profilers.each do |profiler| 102 | expect(profiler).to receive(:reset!) 103 | end 104 | 105 | expect { subject }.to change(profiler_engine, :request_key).from(nil).to("GET SampleController#index{format:html}") 106 | end 107 | end 108 | 109 | describe "sql.active_record" do 110 | let(:profiler_database_request) do 111 | profiler_engine.profilers.find { |profiler| profiler.instance_of?(PigCI::Profiler::DatabaseRequest) } 112 | end 113 | let(:payload) { {} } 114 | 115 | subject do 116 | ActiveSupport::Notifications.instrument("sql.active_record", payload) {} 117 | end 118 | 119 | it do 120 | expect(profiler_database_request).to_not receive(:increment!) 121 | subject 122 | end 123 | 124 | context "with a request_key set" do 125 | before { profiler_engine.request_key = "request-key" } 126 | 127 | it do 128 | expect(profiler_database_request).to receive(:increment!) 129 | subject 130 | end 131 | 132 | context "with a cached query" do 133 | let(:payload) { {cached: true} } 134 | 135 | it do 136 | expect(profiler_database_request).to receive(:increment!) 137 | subject 138 | end 139 | 140 | context "With PigCI#ignore_cached_queries set to true" do 141 | before { PigCI.ignore_cached_queries = true } 142 | after { PigCI.ignore_cached_queries = false } 143 | 144 | it do 145 | expect(profiler_database_request).to_not receive(:increment!) 146 | subject 147 | end 148 | end 149 | end 150 | 151 | context "with PigCI#enabled set to false" do 152 | before { PigCI.enabled = false } 153 | after { PigCI.enabled = true } 154 | 155 | it do 156 | expect(profiler_database_request).to_not receive(:increment!) 157 | subject 158 | end 159 | end 160 | end 161 | end 162 | 163 | describe "process_action.action_controller" do 164 | let(:payload) {} 165 | before do 166 | profiler_engine.request_key = "request-key" 167 | end 168 | 169 | subject do 170 | ActiveSupport::Notifications.instrument("process_action.action_controller", payload) {} 171 | end 172 | 173 | it do 174 | profiler_engine.profilers.each do |profiler| 175 | expect(profiler).to receive(:log_request!) 176 | end 177 | 178 | expect { subject }.to change(profiler_engine, :request_captured).from(false).to(true) 179 | .and change(profiler_engine, :request_key).from("request-key").to(nil) 180 | end 181 | 182 | context "with PigCI#enabled set to false" do 183 | before { PigCI.enabled = false } 184 | after { PigCI.enabled = true } 185 | 186 | it do 187 | profiler_engine.profilers.each do |profiler| 188 | expect(profiler).to_not receive(:log_request!) 189 | end 190 | 191 | expect { subject }.to_not change(profiler_engine, :request_captured) 192 | end 193 | 194 | it do 195 | expect { subject }.to change(profiler_engine, :request_key).from("request-key").to(nil) 196 | end 197 | end 198 | end 199 | end 200 | end 201 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/profiler_engine_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::ProfilerEngine do 4 | let(:profiler_engine) { PigCI::ProfilerEngine.new } 5 | 6 | describe "#request_key" do 7 | subject { profiler_engine.request_key } 8 | 9 | it do 10 | profiler_engine.request_key = "some-key" 11 | is_expected.to eq("some-key") 12 | end 13 | end 14 | 15 | describe "#request_key?" do 16 | subject { profiler_engine.request_key? } 17 | 18 | it do 19 | expect { profiler_engine.request_key = "some-key" }.to change(profiler_engine, :request_key?).from(false).to(true) 20 | end 21 | end 22 | 23 | describe "#request_captured!" do 24 | subject { profiler_engine.request_captured! } 25 | 26 | it do 27 | expect { subject }.to change(profiler_engine, :request_captured?).from(false).to(true) 28 | end 29 | end 30 | 31 | describe "#profilers" do 32 | subject { profiler_engine.profilers } 33 | 34 | it { is_expected.to eq([]) } 35 | end 36 | 37 | describe "#reports" do 38 | subject { profiler_engine.reports } 39 | 40 | it { is_expected.to eq([]) } 41 | end 42 | 43 | describe "#setup!" do 44 | subject { profiler_engine.setup! } 45 | 46 | it { expect { subject }.to raise_error(NotImplementedError) } 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/profiler_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Profiler do 4 | let(:profiler) { PigCI::Profiler.new } 5 | 6 | describe "#setup!" do 7 | subject { profiler.setup! } 8 | 9 | it "clears the previous run data" do 10 | # Write some blank data 11 | File.write(profiler.log_file, "some-data") 12 | 13 | expect { subject }.to change(profiler.log_file, :read).from("some-data").to("") 14 | end 15 | end 16 | 17 | describe "#reset!" do 18 | subject { profiler.reset! } 19 | 20 | it "clears the log value" do 21 | profiler.log_value = 12 22 | 23 | expect { subject }.to change(profiler, :log_value).from(12).to(0) 24 | end 25 | end 26 | 27 | describe "#increment!" do 28 | subject { profiler.increment! } 29 | 30 | it { expect { subject }.to raise_error(NotImplementedError) } 31 | end 32 | 33 | describe "#log_request!(request_key)" do 34 | subject { profiler.log_request!("request-key") } 35 | 36 | before { profiler.setup! } 37 | 38 | it "saves the log value with the request key" do 39 | profiler.log_value = 23 40 | 41 | expect { subject }.to change(profiler.log_file, :read).from("").to("request-key|23\n") 42 | end 43 | end 44 | 45 | describe "#log_file" do 46 | subject { profiler.log_file.to_s } 47 | 48 | it { is_expected.to match("profiler.txt") } 49 | 50 | context "can be defined on initialize" do 51 | let(:profiler) { PigCI::Profiler.new(log_file: "something-else") } 52 | 53 | it { is_expected.to eq("something-else") } 54 | end 55 | end 56 | 57 | describe "#historical_log_file" do 58 | subject { profiler.historical_log_file.to_s } 59 | 60 | it { is_expected.to match("profiler.json") } 61 | 62 | context "can be defined on initialize" do 63 | let(:profiler) { PigCI::Profiler.new(historical_log_file: "something-else") } 64 | 65 | it { is_expected.to eq("something-else") } 66 | end 67 | end 68 | 69 | describe "#i18n_key" do 70 | subject { profiler.i18n_key } 71 | 72 | it { is_expected.to eq("profiler") } 73 | 74 | context "can be defined on initialize" do 75 | let(:profiler) { PigCI::Profiler.new(i18n_key: "something-else") } 76 | 77 | it { is_expected.to eq("something-else") } 78 | end 79 | end 80 | 81 | describe "#save!" do 82 | subject { profiler.save! } 83 | 84 | let(:expected_output) { JSON.parse(File.open(File.join("spec", "fixtures", "files", "profiler.json")).read) } 85 | 86 | before do 87 | profiler.log_file = File.join("spec", "fixtures", "files", "profiler.txt") 88 | File.write(profiler.historical_log_file, "{}") 89 | end 90 | 91 | it "takes the profiler data, and saves in a JSON format with previous test runs" do 92 | expect { subject }.to change { JSON.parse(profiler.historical_log_file.read) }.from({}).to(expected_output) 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/report/memory_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Report::Memory do 4 | let(:report) { PigCI::Report::Memory.new } 5 | 6 | describe "::format_row" do 7 | subject { PigCI::Report::Memory.format_row(row) } 8 | let(:row) do 9 | { 10 | max: 1_048_576, 11 | mean: 1_048_576, 12 | min: 1_048_576, 13 | total: 1_048_576, 14 | number_of_requests: 1, 15 | max_change_percentage: 0.0 16 | } 17 | end 18 | 19 | it do 20 | is_expected.to eq( 21 | { 22 | max: BigDecimal("1"), 23 | mean: BigDecimal("1"), 24 | min: BigDecimal("1"), 25 | total: BigDecimal("1"), 26 | number_of_requests: 1, 27 | max_change_percentage: "0.0%" 28 | } 29 | ) 30 | end 31 | end 32 | 33 | describe "::bytes_in_a_megabyte" do 34 | subject { PigCI::Report::Memory.bytes_in_a_megabyte } 35 | 36 | it { is_expected.to eq(BigDecimal("1_048_576")) } 37 | end 38 | 39 | describe "#i18n_scope" do 40 | subject { report.i18n_scope } 41 | 42 | it { is_expected.to eq("pig_ci.report.memory") } 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/report_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Report do 4 | let(:report) { PigCI::Report.new } 5 | 6 | describe "::format_row" do 7 | subject { PigCI::Report.format_row(row) } 8 | let(:row) do 9 | { 10 | max: 1_048_576, 11 | mean: 1_048_576, 12 | min: 1_048_576, 13 | total: 1_048_576, 14 | number_of_requests: 1, 15 | max_change_percentage: 0.0 16 | } 17 | end 18 | 19 | it do 20 | is_expected.to eq( 21 | { 22 | max: 1_048_576, 23 | mean: 1_048_576, 24 | min: 1_048_576, 25 | total: 1_048_576, 26 | number_of_requests: 1, 27 | max_change_percentage: "0.0%" 28 | } 29 | ) 30 | end 31 | end 32 | 33 | describe "#max_for" do 34 | subject { report.max_for("1000") } 35 | 36 | let(:rows) do 37 | [ 38 | {max: 576}, 39 | {max: 1_048_576}, 40 | {max: 576} 41 | ] 42 | end 43 | 44 | before do 45 | allow(report).to receive(:sorted_and_formatted_data_for).and_return(rows) 46 | end 47 | 48 | it { is_expected.to eq(1_048_576) } 49 | end 50 | 51 | describe "#over_threshold_for?" do 52 | subject { report.over_threshold_for?(100) } 53 | 54 | it "returns true when under threshold" do 55 | expect(report).to receive(:max_for).with(100).and_return(100).twice 56 | expect(report).to receive(:threshold).and_return(90).twice 57 | 58 | is_expected.to be true 59 | end 60 | end 61 | 62 | describe "#i18n_scope" do 63 | subject { report.i18n_scope } 64 | 65 | it { is_expected.to eq("pig_ci.report.report") } 66 | end 67 | 68 | context "i18n_key is memory" do 69 | let(:report) { PigCI::Report.new(i18n_key: "memory") } 70 | 71 | describe "#i18n_name" do 72 | subject { report.i18n_name } 73 | 74 | it { is_expected.to eq("Peak memory per a request") } 75 | end 76 | 77 | describe "#headings" do 78 | subject { report.headings } 79 | 80 | it { is_expected.to eq(["Key", "Max (MB)", "Min (MB)", "Mean (MB)", "Requests", "% Change"]) } 81 | end 82 | 83 | describe "#threshold" do 84 | subject { report.threshold } 85 | 86 | it { is_expected.to eq(350) } 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/summary/ci_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Summary::CI do 4 | let(:reports) do 5 | [ 6 | PigCI::Report.new(i18n_key: "profiler", historical_log_file: File.join("spec", "fixtures", "files", "profiler.json")) 7 | ] 8 | end 9 | 10 | describe "#call!" do 11 | let(:profiler_sample_log_data) { File.open(File.join("spec", "fixtures", "files", "profiler.txt")).read } 12 | subject { described_class.new(reports: reports).call! } 13 | 14 | it "Outputs the data to terminal screen without stopping CI" do 15 | expect(Kernel).to_not receive(:exit) 16 | expect { subject }.to output(/translation missing: en.pig_ci.report.profiler.name/).to_stdout 17 | end 18 | 19 | context "report is over the threshold" do 20 | before do 21 | allow(reports[0]).to receive(:over_threshold_for?).and_return(true) 22 | end 23 | 24 | it "Outputs the data to terminal screen without stopping CI" do 25 | expect(Kernel).to receive(:exit).with(2) 26 | expect { subject }.to output(/PigCI: This commit has exceeded the thresholds defined in PigCI\.thresholds/).to_stdout 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/lib/pig_ci/summary/html_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PigCI::Summary::HTML do 4 | let(:reports) do 5 | [ 6 | PigCI::Report::Memory.new, 7 | PigCI::Report::RequestTime.new, 8 | PigCI::Report::DatabaseRequest.new 9 | ] 10 | end 11 | 12 | describe "#save!" do 13 | subject { PigCI::Summary::HTML.new(reports: reports).save! } 14 | 15 | let(:index_file) { PigCI.output_directory.join("index.html") } 16 | 17 | before do 18 | # Maybe also load up some files with some sample JSON. 19 | File.open(index_file, "w") { |file| file.truncate(0) } 20 | end 21 | 22 | it "writes an html index file containing the the json" do 23 | expect { subject }.to change(index_file, :read).from("").to(/