flutter 0.1.0.pre.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -5
- data/Gemfile +1 -0
- data/README.md +62 -2
- data/Rakefile +35 -1
- data/codecov.yml +13 -0
- data/lib/flutter/minitest.rb +6 -8
- data/lib/flutter/parser.rb +18 -16
- data/lib/flutter/persistence.rb +51 -5
- data/lib/flutter/rspec.rb +7 -7
- data/lib/flutter/tracker.rb +48 -5
- data/lib/flutter/version.rb +1 -1
- metadata +21 -95
- data/integration_tests/minitest/grape_app/.gitignore +0 -67
- data/integration_tests/minitest/grape_app/.ruby-version +0 -1
- data/integration_tests/minitest/grape_app/Gemfile +0 -17
- data/integration_tests/minitest/grape_app/Gemfile.lock +0 -89
- data/integration_tests/minitest/grape_app/README.md +0 -2
- data/integration_tests/minitest/grape_app/Rakefile +0 -9
- data/integration_tests/minitest/grape_app/api/api.rb +0 -34
- data/integration_tests/minitest/grape_app/api/routes/api_helpers.rb +0 -12
- data/integration_tests/minitest/grape_app/api/routes/change_request/api.rb +0 -69
- data/integration_tests/minitest/grape_app/api/routes/change_request/response_entity.rb +0 -18
- data/integration_tests/minitest/grape_app/api/routes/event/api.rb +0 -121
- data/integration_tests/minitest/grape_app/api/routes/event/response_entity.rb +0 -41
- data/integration_tests/minitest/grape_app/api/routes/project/api.rb +0 -59
- data/integration_tests/minitest/grape_app/api/routes/project/response_entity.rb +0 -13
- data/integration_tests/minitest/grape_app/api/routes/property/api.rb +0 -78
- data/integration_tests/minitest/grape_app/api/routes/property/response_entity.rb +0 -31
- data/integration_tests/minitest/grape_app/api/routes/trackable_object/api.rb +0 -64
- data/integration_tests/minitest/grape_app/api/routes/trackable_object/response_entity.rb +0 -24
- data/integration_tests/minitest/grape_app/api/routes/tracking_spec/api.rb +0 -88
- data/integration_tests/minitest/grape_app/api/routes/tracking_spec/response_entity.rb +0 -17
- data/integration_tests/minitest/grape_app/api/routes/tracking_spec/spec_response.rb +0 -19
- data/integration_tests/minitest/grape_app/api/routes/user/api.rb +0 -22
- data/integration_tests/minitest/grape_app/api/routes/user/response_entity.rb +0 -12
- data/integration_tests/minitest/grape_app/app/app.rb +0 -30
- data/integration_tests/minitest/grape_app/app/change_request/endpoint.rb +0 -24
- data/integration_tests/minitest/grape_app/app/change_request/generate_system_changes.rb +0 -25
- data/integration_tests/minitest/grape_app/app/change_request/service.rb +0 -79
- data/integration_tests/minitest/grape_app/app/event/endpoint.rb +0 -78
- data/integration_tests/minitest/grape_app/app/event/service.rb +0 -68
- data/integration_tests/minitest/grape_app/app/event/validator.rb +0 -108
- data/integration_tests/minitest/grape_app/app/project/endpoint.rb +0 -24
- data/integration_tests/minitest/grape_app/app/project/service.rb +0 -42
- data/integration_tests/minitest/grape_app/app/property/endpoint.rb +0 -39
- data/integration_tests/minitest/grape_app/app/property/service.rb +0 -56
- data/integration_tests/minitest/grape_app/app/trackable_object/endpoint.rb +0 -38
- data/integration_tests/minitest/grape_app/app/trackable_object/service.rb +0 -49
- data/integration_tests/minitest/grape_app/app/trackable_object/validator.rb +0 -40
- data/integration_tests/minitest/grape_app/app/tracking_spec/endpoint.rb +0 -58
- data/integration_tests/minitest/grape_app/app/tracking_spec/expand_tracking_spec_events.rb +0 -91
- data/integration_tests/minitest/grape_app/app/tracking_spec/service.rb +0 -51
- data/integration_tests/minitest/grape_app/app/tracking_spec/validator.rb +0 -61
- data/integration_tests/minitest/grape_app/app/utils/errors/api_exceptions.rb +0 -10
- data/integration_tests/minitest/grape_app/app/utils/errors/service_exceptions.rb +0 -12
- data/integration_tests/minitest/grape_app/app/versioned_entity/entity.rb +0 -110
- data/integration_tests/minitest/grape_app/app/versioned_entity/service.rb +0 -47
- data/integration_tests/minitest/grape_app/app/versioned_entity/validator.rb +0 -61
- data/integration_tests/minitest/grape_app/app/versioned_entity_snapshot/entity.rb +0 -32
- data/integration_tests/minitest/grape_app/config/boot.rb +0 -9
- data/integration_tests/minitest/grape_app/config.ru +0 -3
- data/integration_tests/minitest/grape_app/db/proto/change_request.rb +0 -10
- data/integration_tests/minitest/grape_app/db/proto/common/doc.rb +0 -86
- data/integration_tests/minitest/grape_app/db/proto/event.rb +0 -10
- data/integration_tests/minitest/grape_app/db/proto/event_snapshot.rb +0 -10
- data/integration_tests/minitest/grape_app/db/proto/project.rb +0 -21
- data/integration_tests/minitest/grape_app/db/proto/property.rb +0 -10
- data/integration_tests/minitest/grape_app/db/proto/property_snapshot.rb +0 -10
- data/integration_tests/minitest/grape_app/db/proto/trackable_object.rb +0 -10
- data/integration_tests/minitest/grape_app/db/proto/trackable_object_snapshot.rb +0 -10
- data/integration_tests/minitest/grape_app/db/proto/tracking_spec.rb +0 -10
- data/integration_tests/minitest/grape_app/test/api/functional/event_test.rb +0 -186
- data/integration_tests/minitest/grape_app/test/api/functional/property_test.rb +0 -197
- data/integration_tests/minitest/grape_app/test/api/functional/trackable_object_test.rb +0 -134
- data/integration_tests/minitest/grape_app/test/api/functional/tracking_spec_test.rb +0 -56
- data/integration_tests/minitest/grape_app/test/api/functional/user_test.rb +0 -24
- data/integration_tests/minitest/grape_app/test/api/functional/versioned_entity_helper.rb +0 -157
- data/integration_tests/minitest/grape_app/test/fixtures/change_requests.rb +0 -83
- data/integration_tests/minitest/grape_app/test/fixtures/event_snapshots.rb +0 -105
- data/integration_tests/minitest/grape_app/test/fixtures/events.rb +0 -58
- data/integration_tests/minitest/grape_app/test/fixtures/properties.rb +0 -66
- data/integration_tests/minitest/grape_app/test/fixtures/property_snapshots.rb +0 -124
- data/integration_tests/minitest/grape_app/test/fixtures/sample_tracking_spec.json +0 -125
- data/integration_tests/minitest/grape_app/test/fixtures/trackable_objects.rb +0 -58
- data/integration_tests/minitest/grape_app/test/fixtures/trackable_objects_snapshots.rb +0 -61
- data/integration_tests/minitest/grape_app/test/fixtures/tracking_specs.rb +0 -22
- data/integration_tests/minitest/grape_app/test/test_helper.rb +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2ca2f5eb98359bf0cbac8abbdedcbe6ca27ec7bf205625f44cf0c4150313eeb4
|
|
4
|
+
data.tar.gz: 6620320c3c54d63117ddc95550c0d4223411bfe9b6b469336fe9f60ffcddcfe3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f97e5720ea6fd890e4c6283a1193750eea3c1ffa64de2de550793cecb14f8636a205774a08811a3f4aea35ed5a764023603fa91a33bd0b4d00496925f4ab35c
|
|
7
|
+
data.tar.gz: 7f890be9219df6f89fde38e9bc34479a65c9a3d8d910ec1495732a2a32ac65d33291c507b8e7b6950facca0d02b6a47c726c864ae2abde9a4416ae983096b29c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
All notable changes to this project will be documented in this file.
|
|
3
|
+
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## Unreleased
|
|
8
|
+
## 0.2.0
|
|
9
|
+
### Added
|
|
10
|
+
- CI Recipe in README
|
|
11
|
+
- CI integration for incremental tests in branches/pull requests
|
|
12
|
+
- Release workflow tasks & github actions integration
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Ensure all methods (including inherited ones) are considered when calculating signatures for a class or module.
|
|
16
|
+
- Fix calculation of total / filtered examples for rspec integration
|
|
17
|
+
|
|
18
|
+
## 0.1.0.pre.3
|
|
19
|
+
### Added
|
|
20
|
+
- Improved documentation for Persistence classes
|
|
21
|
+
- Improved documentation for Tracker
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Pinned dependencies to known working minimum versions
|
|
25
|
+
|
|
26
|
+
## 0.1.0.pre.2
|
|
27
|
+
### Added
|
|
28
|
+
- Minitest integration
|
|
29
|
+
- RSpec integration
|
|
6
30
|
|
data/Gemfile
CHANGED
|
@@ -11,6 +11,7 @@ group :test, :development do
|
|
|
11
11
|
gem "overcommit", "~> 0.59.1"
|
|
12
12
|
gem "gem-release", "~> 2.2"
|
|
13
13
|
gem "guard", "~> 2.18"
|
|
14
|
+
gem "keepachangelog", "~> 0.6.1"
|
|
14
15
|
gem "rubocop", "~> 1.21"
|
|
15
16
|
gem "rubocop-shopify", require: false
|
|
16
17
|
gem "rubocop-minitest", "~> 0.22.1"
|
data/README.md
CHANGED
|
@@ -107,7 +107,55 @@ guard :rspec, cmd: "rspec" do
|
|
|
107
107
|
end
|
|
108
108
|
```
|
|
109
109
|
## Configuring flutter in continuous integration
|
|
110
|
-
|
|
110
|
+
|
|
111
|
+
Flutter can be used in continuous integration environments to speed up the turn
|
|
112
|
+
around time from running tests by only running tests affected by the changes
|
|
113
|
+
in a pull request.
|
|
114
|
+
|
|
115
|
+
### Github Actions
|
|
116
|
+
The following example workflow with github actions does the following:
|
|
117
|
+
- Always run all tests on the `main` branch
|
|
118
|
+
- Only run tests affected by the "current" commit for CI workflows triggered by a `push` event on other branches
|
|
119
|
+
- If the CI workflow is triggered due to a `pull_request` event, run all tests affected by all commits in the branch
|
|
120
|
+
(by comparing against the branch point of the pull request)
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
# Get the commit where this branch diverges from origin/main
|
|
124
|
+
- name: Retrieve branch point
|
|
125
|
+
if: github.event_name == 'pull_request'
|
|
126
|
+
run: |
|
|
127
|
+
echo "::set-output name=KEY::$(diff -u <(git rev-list --first-parent origin/main) <(git rev-list --first-parent HEAD) | sed -ne 's/^ //p' | head -1)"
|
|
128
|
+
id: cache_keys
|
|
129
|
+
# Use the always-upload-cache action to:
|
|
130
|
+
# - Restore the flutter state from cache from either the branch point (if it was set in the previous step)
|
|
131
|
+
# or the last run in the current branch
|
|
132
|
+
# - After the run cache the flutter state using the current commit hash as the hash key
|
|
133
|
+
- name: Setup flutter state
|
|
134
|
+
id: flutter-state
|
|
135
|
+
uses: pat-s/always-upload-cache@v2.1.5
|
|
136
|
+
env:
|
|
137
|
+
cache-name: cache-flutter-state
|
|
138
|
+
with:
|
|
139
|
+
path: .flutter
|
|
140
|
+
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.ruby-version }}-${{ github.sha }}
|
|
141
|
+
restore-keys: |
|
|
142
|
+
${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.ruby-version }}-${{ steps.cache_keys.outputs.KEY }}
|
|
143
|
+
# If this is a push event on the main branch, clear the flutter state
|
|
144
|
+
# so that all tests are run and a full state is cached on the main branch
|
|
145
|
+
- name: Clear flutter state
|
|
146
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/main')
|
|
147
|
+
run: rm -rf .flutter
|
|
148
|
+
```
|
|
149
|
+
> **Note**
|
|
150
|
+
> The exact CI configuration would ofcourse depend on your workflow and confidence in selectively
|
|
151
|
+
> running tests for pull requests.
|
|
152
|
+
|
|
153
|
+
> **Warning**
|
|
154
|
+
> Selectively running tests in a pull request would show a drop in coverage if you are collecting
|
|
155
|
+
> and/or using code coverage as a "Check". One way to make Flutter work hand in hand with code
|
|
156
|
+
> coverage checks is to only validate that the diff in the pull request has a 100% coverage. For
|
|
157
|
+
> example with [codecov](https://docs.codecov.com/docs/commit-status#section-project-status) this can be
|
|
158
|
+
> achieved by only enabling the `project` status for the main branch and `patch` status otherwise.
|
|
111
159
|
|
|
112
160
|
## Related work
|
|
113
161
|
|
|
@@ -119,7 +167,19 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
|
119
167
|
|
|
120
168
|
This project uses [overcommit](https://github.com/sds/overcommit) to enforce standards. Enable the precommit hooks in your local checkout by running: `overcommit --sign`
|
|
121
169
|
|
|
122
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
170
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
171
|
+
|
|
172
|
+
### Releasing a new version
|
|
173
|
+
- Ensure that the [Unreleased](./CHANGELOG.md#Unreleased) section of the changelog is up to date
|
|
174
|
+
and contains useful details.
|
|
175
|
+
- Create a new release using the `release` rake task as follows (for more details about specifying the version change
|
|
176
|
+
run `gem bump --help` which is the command used by the task):
|
|
177
|
+
- Patch release `bundle exec rake release["-v patch"]`
|
|
178
|
+
- Minor release `bundle exec rake release["-v minor"]`
|
|
179
|
+
- Major release `bundle exec rake release["-v major"]`
|
|
180
|
+
> **Note**
|
|
181
|
+
> The `release` rake task automates updating the changelog & version, committing the changes & creating a new tag
|
|
182
|
+
- Push the tag. The CI workflow for tag pushes will take care of publishing the gem & creating a github release.
|
|
123
183
|
|
|
124
184
|
## Contributing
|
|
125
185
|
|
data/Rakefile
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/gem_tasks"
|
|
4
3
|
require "rake/testtask"
|
|
4
|
+
require "keepachangelog"
|
|
5
5
|
|
|
6
6
|
Rake::TestTask.new(:unit) do |t|
|
|
7
7
|
t.libs << "test"
|
|
@@ -22,3 +22,37 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
22
22
|
|
|
23
23
|
task test: [:unit, :spec]
|
|
24
24
|
task default: [:test, :spec, "rubocop:autocorrect_all"]
|
|
25
|
+
|
|
26
|
+
desc "Increment the version, update changelog and create a tag for the release"
|
|
27
|
+
task :release, [:version] do |_t, args|
|
|
28
|
+
parser = Keepachangelog::MarkdownParser.load("CHANGELOG.md")
|
|
29
|
+
log = parser.parsed_content["versions"].delete("Unreleased")
|
|
30
|
+
sh("gem bump --pretend #{args[:version]}") do |ok, _|
|
|
31
|
+
if ok
|
|
32
|
+
new_version = %x(gem bump --no-commit #{args[:version]} | awk '{print $4}' | uniq).chomp
|
|
33
|
+
parser.parsed_content["versions"]["Unreleased"] = { "url" => nil, "date" => nil, "changes" => {} }
|
|
34
|
+
parser.parsed_content["versions"][new_version] = log
|
|
35
|
+
File.open("CHANGELOG.md", "w") do |file|
|
|
36
|
+
file.write(parser.to_md)
|
|
37
|
+
end
|
|
38
|
+
%x(git add CHANGELOG.md lib/flutter/version.rb)
|
|
39
|
+
%x(git commit -m "Bump flutter to #{new_version}")
|
|
40
|
+
%x(gem tag -s)
|
|
41
|
+
Rake::Task["release_notes"].execute({ version: new_version })
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "Get release notes for the current or specific version"
|
|
47
|
+
task :release_notes, [:version] do |_t, args|
|
|
48
|
+
version = (args[:version] || Flutter::VERSION)
|
|
49
|
+
parser = Keepachangelog::MarkdownParser.load("CHANGELOG.md")
|
|
50
|
+
parser.parsed_content.delete("intro")
|
|
51
|
+
parser.parsed_content.delete("title")
|
|
52
|
+
parser.parsed_content["versions"] = parser.parsed_content["versions"].select { |k, _v| k == version }
|
|
53
|
+
lines = parser.to_md.split("\n")
|
|
54
|
+
chunk = ["## #{version}"] + (
|
|
55
|
+
lines.slice_after { |line| line.include?("## #{version}") }.to_a[1] || []
|
|
56
|
+
)
|
|
57
|
+
puts chunk.join("\n")
|
|
58
|
+
end
|
data/codecov.yml
ADDED
data/lib/flutter/minitest.rb
CHANGED
|
@@ -10,7 +10,7 @@ end
|
|
|
10
10
|
module Flutter
|
|
11
11
|
module Minitest
|
|
12
12
|
class << self
|
|
13
|
-
attr_accessor :filtered
|
|
13
|
+
attr_accessor :filtered, :total
|
|
14
14
|
|
|
15
15
|
def flutter_tracker
|
|
16
16
|
@tracker ||= Flutter::Tracker.new(
|
|
@@ -31,7 +31,7 @@ module Flutter
|
|
|
31
31
|
return unless ::Flutter.enabled
|
|
32
32
|
|
|
33
33
|
Flutter::Minitest.flutter_tracker.persist!
|
|
34
|
-
$stdout.puts "Flutter filtered
|
|
34
|
+
$stdout.puts "Flutter filtered #{Flutter::Minitest.filtered} / #{Flutter::Minitest.total} tests"
|
|
35
35
|
if @verbose
|
|
36
36
|
$stdout.puts "Persisted flutter #{Flutter::Minitest.flutter_tracker}"
|
|
37
37
|
end
|
|
@@ -43,17 +43,15 @@ module Flutter
|
|
|
43
43
|
module ClassMethods
|
|
44
44
|
def runnable_methods
|
|
45
45
|
Flutter::Minitest.filtered ||= 0
|
|
46
|
+
Flutter::Minitest.total ||= 0
|
|
46
47
|
default = super()
|
|
48
|
+
Flutter::Minitest.total += default.length
|
|
47
49
|
default.select do |test|
|
|
48
|
-
|
|
50
|
+
!Minitest.flutter_tracker.skip?(
|
|
49
51
|
"#{name}##{test}",
|
|
50
52
|
File.absolute_path(instance_method(test).source_location[0]),
|
|
51
53
|
instance_method(test).source,
|
|
52
|
-
)
|
|
53
|
-
if skip
|
|
54
|
-
Flutter::Minitest.filtered += 1
|
|
55
|
-
end
|
|
56
|
-
!skip
|
|
54
|
+
).tap { |skip| Flutter::Minitest.filtered += 1 if skip }
|
|
57
55
|
end
|
|
58
56
|
end
|
|
59
57
|
end
|
data/lib/flutter/parser.rb
CHANGED
|
@@ -8,6 +8,11 @@ module Flutter
|
|
|
8
8
|
class Parser
|
|
9
9
|
attr_reader :signatures
|
|
10
10
|
|
|
11
|
+
@method_cache = {}
|
|
12
|
+
class << self
|
|
13
|
+
attr_reader :method_cache
|
|
14
|
+
end
|
|
15
|
+
|
|
11
16
|
def initialize(file)
|
|
12
17
|
@signatures = {}
|
|
13
18
|
@targets = Set.new
|
|
@@ -49,23 +54,17 @@ module Flutter
|
|
|
49
54
|
require_relative @file
|
|
50
55
|
@targets.each do |container|
|
|
51
56
|
instance = Kernel.const_get(container)
|
|
52
|
-
class_methods =
|
|
53
|
-
|
|
54
|
-
) + (
|
|
55
|
-
instance.private_methods - Object.private_methods
|
|
56
|
-
)
|
|
57
|
-
instance_methods = (
|
|
58
|
-
instance.instance_methods - Object.instance_methods
|
|
59
|
-
) + (
|
|
60
|
-
instance.private_instance_methods - Object.private_instance_methods
|
|
61
|
-
)
|
|
57
|
+
class_methods = instance.methods + instance.private_methods
|
|
58
|
+
instance_methods = instance.instance_methods + instance.private_instance_methods
|
|
62
59
|
|
|
63
60
|
@signatures.merge!(class_methods.map do |method|
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
hash = source_hash(instance.method(method))
|
|
62
|
+
["#{container}::#{method}", hash] if hash
|
|
63
|
+
end.compact.to_h)
|
|
66
64
|
@signatures.merge!(instance_methods.map do |method|
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
hash = source_hash(instance.instance_method(method))
|
|
66
|
+
["#{container}:#{method}", hash] if hash
|
|
67
|
+
end.compact.to_h)
|
|
69
68
|
rescue NameError
|
|
70
69
|
$stderr.puts "failed to load #{container} in #{@file}"
|
|
71
70
|
end
|
|
@@ -74,9 +73,12 @@ module Flutter
|
|
|
74
73
|
end
|
|
75
74
|
|
|
76
75
|
def source_hash(callable)
|
|
77
|
-
|
|
76
|
+
return unless callable.source_location
|
|
77
|
+
|
|
78
|
+
sep = callable.is_a?(UnboundMethod) ? ":" : "::"
|
|
79
|
+
Parser.method_cache["#{callable.owner}#{sep}#{callable.name}"] ||= Digest::SHA1.hexdigest(callable.source)
|
|
78
80
|
rescue MethodSource::SourceNotFoundError
|
|
79
|
-
|
|
81
|
+
nil
|
|
80
82
|
end
|
|
81
83
|
end
|
|
82
84
|
end
|
data/lib/flutter/persistence.rb
CHANGED
|
@@ -4,40 +4,68 @@ require "fileutils"
|
|
|
4
4
|
require "set"
|
|
5
5
|
module Flutter
|
|
6
6
|
module Persistence
|
|
7
|
+
# The abstract base storage.
|
|
8
|
+
#
|
|
9
|
+
# To implement a custom storage, override the following methods:
|
|
10
|
+
# * {#test_mapping}
|
|
11
|
+
# * {#source_mapping}
|
|
12
|
+
# * {#update_test_mapping!}
|
|
13
|
+
# * {#update_source_mapping!}
|
|
14
|
+
# * {#load!}
|
|
15
|
+
# * {#persist!}
|
|
16
|
+
# * {#clear!}
|
|
17
|
+
#
|
|
18
|
+
# @abstract Override this class to implement a custom storage
|
|
7
19
|
class AbstractStorage
|
|
8
20
|
def initialize
|
|
9
21
|
load!
|
|
10
22
|
end
|
|
11
23
|
|
|
12
24
|
# :nocov:
|
|
25
|
+
|
|
26
|
+
# Mapping of +test_id -> source file -> callable_id+
|
|
27
|
+
# @return [Hash<String, Hash<String, Set<String>>>]
|
|
13
28
|
def test_mapping
|
|
14
29
|
raise NotImplementedError
|
|
15
30
|
end
|
|
16
31
|
|
|
32
|
+
# Mapping of +source file -> callable_id -> signature+
|
|
33
|
+
# @return [Hash<String, Hash<String, String>>] mapping
|
|
17
34
|
def source_mapping
|
|
18
35
|
raise NotImplementedError
|
|
19
36
|
end
|
|
20
37
|
|
|
38
|
+
##
|
|
39
|
+
# Update {#test_mapping}
|
|
40
|
+
#
|
|
41
|
+
# @param [Hash<String, Hash<String, String>>] mapping
|
|
21
42
|
def update_test_mapping!(mapping)
|
|
22
43
|
raise NotImplementedError
|
|
23
44
|
end
|
|
24
45
|
|
|
46
|
+
##
|
|
47
|
+
# Update {#source_mapping}
|
|
48
|
+
#
|
|
49
|
+
# @param [Hash<String, Hash<String, String>>] mapping
|
|
25
50
|
def update_source_mapping!(mapping)
|
|
26
51
|
raise NotImplementedError
|
|
27
52
|
end
|
|
28
53
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def to_s
|
|
54
|
+
# Save the state of test & source mapping to the underlying
|
|
55
|
+
# storage
|
|
56
|
+
# @return [void]
|
|
57
|
+
def persist!
|
|
34
58
|
raise NotImplementedError
|
|
35
59
|
end
|
|
36
60
|
|
|
61
|
+
# Clear any saved state in the underlying storage
|
|
62
|
+
# @return [void]
|
|
37
63
|
def clear!
|
|
38
64
|
raise NotImplementedError
|
|
39
65
|
end
|
|
40
66
|
|
|
67
|
+
# If the storage was already persisted load the current state
|
|
68
|
+
# @return [void]
|
|
41
69
|
def load!
|
|
42
70
|
raise NotImplementedError
|
|
43
71
|
end
|
|
@@ -48,6 +76,8 @@ module Flutter
|
|
|
48
76
|
require "yaml"
|
|
49
77
|
# ruby >= 3.1 requires this
|
|
50
78
|
YAML_LOAD_OPTS = RUBY_VERSION > "3.1" ? { permitted_classes: [Hash, Set, Symbol] } : {}
|
|
79
|
+
|
|
80
|
+
# @param [String] path The directory to store the +state.yml+ file
|
|
51
81
|
def initialize(path:)
|
|
52
82
|
@path = File.absolute_path(path)
|
|
53
83
|
@full_path = File.join(@path, "state.yml")
|
|
@@ -55,6 +85,7 @@ module Flutter
|
|
|
55
85
|
super()
|
|
56
86
|
end
|
|
57
87
|
|
|
88
|
+
# (see AbstractStorage#load!)
|
|
58
89
|
def load!
|
|
59
90
|
if File.exist?(@full_path)
|
|
60
91
|
persisted = YAML.load(File.read(@full_path), **YAML_LOAD_OPTS)
|
|
@@ -62,27 +93,33 @@ module Flutter
|
|
|
62
93
|
end
|
|
63
94
|
end
|
|
64
95
|
|
|
96
|
+
# (see AbstractStorage#test_mapping)
|
|
65
97
|
def test_mapping
|
|
66
98
|
@state.fetch(:test_mapping) { @state[:test_mapping] = {} }
|
|
67
99
|
end
|
|
68
100
|
|
|
101
|
+
# (see AbstractStorage#source_mapping)
|
|
69
102
|
def source_mapping
|
|
70
103
|
@state.fetch(:source_mapping) { @state[:source_mapping] = {} }
|
|
71
104
|
end
|
|
72
105
|
|
|
106
|
+
# (see AbstractStorage#update_test_mapping!)
|
|
73
107
|
def update_test_mapping!(mapping)
|
|
74
108
|
test_mapping.merge!(mapping)
|
|
75
109
|
end
|
|
76
110
|
|
|
111
|
+
# (see AbstractStorage#update_source_mapping!)
|
|
77
112
|
def update_source_mapping!(mapping)
|
|
78
113
|
source_mapping.merge!(mapping)
|
|
79
114
|
end
|
|
80
115
|
|
|
116
|
+
# (see AbstractStorage#clear!)
|
|
81
117
|
def clear!
|
|
82
118
|
FileUtils.rm(@full_path) if File.exist?(@full_path)
|
|
83
119
|
@state.clear
|
|
84
120
|
end
|
|
85
121
|
|
|
122
|
+
# (see AbstractStorage#persist!)
|
|
86
123
|
def persist!
|
|
87
124
|
FileUtils.mkdir_p(@path) unless File.exist?(@path)
|
|
88
125
|
File.open(@full_path, "w") { |file| file.write(@state.to_yaml) }
|
|
@@ -95,6 +132,7 @@ module Flutter
|
|
|
95
132
|
|
|
96
133
|
class Marshal < AbstractStorage
|
|
97
134
|
require "pstore"
|
|
135
|
+
# @param [String] path The directory to store the marshaled state file +state.pstore+
|
|
98
136
|
def initialize(path:)
|
|
99
137
|
@path = File.absolute_path(path)
|
|
100
138
|
FileUtils.mkdir_p(@path) unless File.exist?(@path)
|
|
@@ -103,6 +141,7 @@ module Flutter
|
|
|
103
141
|
super()
|
|
104
142
|
end
|
|
105
143
|
|
|
144
|
+
# (see AbstractStorage#load!)
|
|
106
145
|
def load!
|
|
107
146
|
@state = PStore.new(@full_path)
|
|
108
147
|
@state.transaction do
|
|
@@ -111,18 +150,21 @@ module Flutter
|
|
|
111
150
|
end
|
|
112
151
|
end
|
|
113
152
|
|
|
153
|
+
# (see AbstractStorage#test_mapping)
|
|
114
154
|
def test_mapping
|
|
115
155
|
@state.transaction do
|
|
116
156
|
return @state[:test_mapping]
|
|
117
157
|
end
|
|
118
158
|
end
|
|
119
159
|
|
|
160
|
+
# (see AbstractStorage#source_mapping)
|
|
120
161
|
def source_mapping
|
|
121
162
|
@state.transaction do
|
|
122
163
|
return @state[:source_mapping]
|
|
123
164
|
end
|
|
124
165
|
end
|
|
125
166
|
|
|
167
|
+
# (see AbstractStorage#update_test_mapping!)
|
|
126
168
|
def update_test_mapping!(mapping)
|
|
127
169
|
@state.transaction do
|
|
128
170
|
@state[:test_mapping] ||= {}
|
|
@@ -130,6 +172,7 @@ module Flutter
|
|
|
130
172
|
end
|
|
131
173
|
end
|
|
132
174
|
|
|
175
|
+
# (see AbstractStorage#update_source_mapping!)
|
|
133
176
|
def update_source_mapping!(mapping)
|
|
134
177
|
@state.transaction do
|
|
135
178
|
@state[:source_mapping] ||= {}
|
|
@@ -137,13 +180,16 @@ module Flutter
|
|
|
137
180
|
end
|
|
138
181
|
end
|
|
139
182
|
|
|
183
|
+
# (see AbstractStorage#clear!)
|
|
140
184
|
def clear!
|
|
141
185
|
FileUtils.rm(@full_path) if File.exist?(@full_path)
|
|
142
186
|
end
|
|
143
187
|
|
|
188
|
+
# (see AbstractStorage#persist!)
|
|
144
189
|
def persist!
|
|
145
190
|
end
|
|
146
191
|
|
|
192
|
+
# @return [String]
|
|
147
193
|
def to_s
|
|
148
194
|
"state: #{@full_path}"
|
|
149
195
|
end
|
data/lib/flutter/rspec.rb
CHANGED
|
@@ -5,7 +5,7 @@ require_relative "tracker"
|
|
|
5
5
|
module Flutter
|
|
6
6
|
module RSpec
|
|
7
7
|
class << self
|
|
8
|
-
attr_accessor :filtered
|
|
8
|
+
attr_accessor :filtered, :total
|
|
9
9
|
|
|
10
10
|
def tracker
|
|
11
11
|
@tracker ||= Flutter::Tracker.new(
|
|
@@ -17,20 +17,20 @@ module Flutter
|
|
|
17
17
|
|
|
18
18
|
module ClassMethods
|
|
19
19
|
def filtered_examples
|
|
20
|
-
Flutter::RSpec.filtered ||=
|
|
20
|
+
Flutter::RSpec.filtered ||= Set.new
|
|
21
|
+
Flutter::RSpec.total ||= Set.new
|
|
21
22
|
Flutter::RSpec.tracker.reset! if Flutter.enabled && Flutter.config.reset_storage
|
|
22
23
|
|
|
23
24
|
original = super
|
|
25
|
+
Flutter::RSpec.total.merge(original)
|
|
24
26
|
return original unless Flutter.enabled
|
|
25
27
|
|
|
26
28
|
original.select do |example|
|
|
27
|
-
|
|
29
|
+
!(example.metadata[:block] && Flutter::RSpec.tracker.skip?(
|
|
28
30
|
example.full_description,
|
|
29
31
|
example.metadata[:absolute_file_path],
|
|
30
32
|
example.metadata[:block].source,
|
|
31
|
-
)
|
|
32
|
-
Flutter::RSpec.filtered += 1 if skip
|
|
33
|
-
!skip
|
|
33
|
+
).tap { |skip| Flutter::RSpec.filtered << example if skip })
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
@@ -58,7 +58,7 @@ if defined?(RSpec.configure)
|
|
|
58
58
|
config.after(:suite) do
|
|
59
59
|
if Flutter.enabled
|
|
60
60
|
$stdout.puts
|
|
61
|
-
$stdout.puts "Flutter filtered
|
|
61
|
+
$stdout.puts "Flutter filtered #{Flutter::RSpec.filtered.length} / #{Flutter::RSpec.total.length} examples"
|
|
62
62
|
Flutter::RSpec.tracker.persist!
|
|
63
63
|
end
|
|
64
64
|
end
|
data/lib/flutter/tracker.rb
CHANGED
|
@@ -7,9 +7,20 @@ require_relative "parser"
|
|
|
7
7
|
require "pry"
|
|
8
8
|
|
|
9
9
|
module Flutter
|
|
10
|
+
# @attr [Hash<String, Hash<String, Set<String>>>] test_mapping Mapping of tests to
|
|
11
|
+
# files -> callable_ids
|
|
12
|
+
# @attr [Hash<String, Hash<String, String>>] source_mapping Mapping of
|
|
13
|
+
# source files -> callable_id -> signature
|
|
14
|
+
# @attr [Persistence::AbstractStorage] storage the storage instance used for
|
|
15
|
+
# persisting the state of the tracker
|
|
10
16
|
class Tracker
|
|
11
|
-
attr_reader :test_mapping, :source_mapping
|
|
17
|
+
attr_reader :test_mapping, :source_mapping, :storage
|
|
12
18
|
|
|
19
|
+
# @param [Array<String>] sources
|
|
20
|
+
# @param [Array<String>] exclusions
|
|
21
|
+
# @param [Class<Flutter::Persistence::AbstractStorage>] storage_class
|
|
22
|
+
# @param [Hash] storage_options Additionally options that should be passed
|
|
23
|
+
# to the +storage_class+ constructor
|
|
13
24
|
def initialize(sources, exclusions, storage_class, storage_options)
|
|
14
25
|
@sources = sources.map { |s| File.absolute_path(s) }
|
|
15
26
|
@exclusions = exclusions.map { |s| File.absolute_path(s) }
|
|
@@ -23,8 +34,8 @@ module Flutter
|
|
|
23
34
|
@tracked_files = {}
|
|
24
35
|
end
|
|
25
36
|
|
|
26
|
-
#
|
|
27
|
-
#
|
|
37
|
+
# Starts tracking calls made by +test+
|
|
38
|
+
# @param [String] test A unique identifier for the test
|
|
28
39
|
def start(test)
|
|
29
40
|
# Delete test from the in-memory mapping to allow each new test run
|
|
30
41
|
# to store all the functions that the test calls into
|
|
@@ -35,10 +46,24 @@ module Flutter
|
|
|
35
46
|
@current_tracepoint&.enable
|
|
36
47
|
end
|
|
37
48
|
|
|
49
|
+
# End tracking (should be called after a call to {#start})
|
|
38
50
|
def stop
|
|
39
51
|
@current_tracepoint&.disable
|
|
40
52
|
end
|
|
41
53
|
|
|
54
|
+
##
|
|
55
|
+
# Decides if a test should be skipped based on *all* of the following
|
|
56
|
+
# criteria being met:
|
|
57
|
+
#
|
|
58
|
+
# 1. Test was seen before
|
|
59
|
+
# 2. Test sources have not changed since the last time it was executed
|
|
60
|
+
# 3. All the callables triggered within the scope of this test have no
|
|
61
|
+
# changes in their source since the last time this test was executed
|
|
62
|
+
#
|
|
63
|
+
# @param [String] test A unique identifier for the test
|
|
64
|
+
# @param [String] test_location The absolute path to the source file containing the test
|
|
65
|
+
# @param [String] test_source The source code of the test itself
|
|
66
|
+
# @return [TrueClass, FalseClass] If the test should be skipped
|
|
42
67
|
def skip?(test, test_location, test_source)
|
|
43
68
|
test_location_rel = relative_path(test_location)
|
|
44
69
|
@test_source_mapping.fetch(test_location_rel) do
|
|
@@ -58,13 +83,22 @@ module Flutter
|
|
|
58
83
|
end.all?
|
|
59
84
|
end
|
|
60
85
|
|
|
86
|
+
##
|
|
87
|
+
# Persist the state of the tracker to the storage
|
|
88
|
+
# specified by {#storage}
|
|
89
|
+
# @return [void]
|
|
61
90
|
def persist!
|
|
62
91
|
@storage.update_test_mapping!(@test_mapping)
|
|
63
92
|
@storage.update_source_mapping!(generate_source_mapping)
|
|
64
93
|
@storage.persist!
|
|
65
94
|
end
|
|
66
95
|
|
|
96
|
+
##
|
|
97
|
+
# Reset the state of the tracker and the storage
|
|
98
|
+
# that it was configured with
|
|
99
|
+
# @return [void]
|
|
67
100
|
def reset!
|
|
101
|
+
$stdout.puts "Resetting flutter: #{@storage}"
|
|
68
102
|
@storage.clear!
|
|
69
103
|
@source_mapping.clear
|
|
70
104
|
@test_mapping.clear
|
|
@@ -74,8 +108,6 @@ module Flutter
|
|
|
74
108
|
@storage.to_s
|
|
75
109
|
end
|
|
76
110
|
|
|
77
|
-
attr_reader :mapping
|
|
78
|
-
|
|
79
111
|
private
|
|
80
112
|
|
|
81
113
|
def hit!(test, tracepoint)
|
|
@@ -116,6 +148,13 @@ module Flutter
|
|
|
116
148
|
end
|
|
117
149
|
end
|
|
118
150
|
|
|
151
|
+
##
|
|
152
|
+
# Check if a file pair should be tracked or not based on the
|
|
153
|
+
# +sources+ and +exclusions+ lists provided when initializing
|
|
154
|
+
# the instance
|
|
155
|
+
#
|
|
156
|
+
# @param [String] file
|
|
157
|
+
# @param [Symbol] _method
|
|
119
158
|
def tracked?(file, _method)
|
|
120
159
|
@tracked_files.fetch(file) do
|
|
121
160
|
@sources.any?(->(source) { File.fnmatch?(source, file) }) && !@exclusions.any?(->(exclusion) {
|
|
@@ -124,6 +163,10 @@ module Flutter
|
|
|
124
163
|
end
|
|
125
164
|
end
|
|
126
165
|
|
|
166
|
+
##
|
|
167
|
+
# Generates a mapping of
|
|
168
|
+
#
|
|
169
|
+
# @return [Hash<String, Hash<String, Hash<String, String>>>]
|
|
127
170
|
def generate_source_mapping
|
|
128
171
|
@test_mapping.map { |_k, v| v.keys }.flatten.uniq.map do |file|
|
|
129
172
|
[file, @current_source_mapping.fetch(file) { Flutter::Parser.new(file).signatures }]
|
data/lib/flutter/version.rb
CHANGED