flutter 0.1.0.pre.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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