appmap_depends 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +159 -0
- data/Rakefile +10 -0
- data/appmap_depends.gemspec +31 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/appmap/depends/api.rb +50 -0
- data/lib/appmap/depends/appmap_js.rb +49 -0
- data/lib/appmap/depends/appmap_js_depends.rb +49 -0
- data/lib/appmap/depends/command_error.rb +15 -0
- data/lib/appmap/depends/git_diff.rb +53 -0
- data/lib/appmap/depends/test_file_inspector.rb +94 -0
- data/lib/appmap/depends/util.rb +24 -0
- data/lib/appmap/depends/version.rb +5 -0
- data/lib/appmap_depends.rb +37 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b1b9b4ea40ab4a29c1a948d170192055e2968066202355029e700f4fc2b81ab7
|
4
|
+
data.tar.gz: 6391eccbb15cca43cd41f6bb3f379559186117122388aefd50b7bd5396b135a9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2c9f085dc5e91d28da82048ff7b91f630caebc8281f126352e03aaedda2303ab8db2d86f5b8e83ac9d5c58e522745ebdcebd59f3fef7a2c76163ff03c5670cce
|
7
|
+
data.tar.gz: 68595be48e354a7bcaaa26731347a1e4593f84fa8418c564ee301ae9b4570a5973d3cdf54772a277f5032cc0917c865739bfbe1dbff3d763031e820be9f9e86e
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Kevin Gilpin
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# `appmap_depends`
|
2
|
+
|
3
|
+
This gem provides Rake tasks called `depends:modified` and `depends:diff`. These Rake tasks automatically compute a list of test cases that need to be re-run based on which source files have been modified. They use [AppMap](https://github.com/applandinc/appmap-ruby) data files, which contain information about test case dependencies on source files, to perform this feat.
|
4
|
+
|
5
|
+
`appmap_depends` depends on an NPM package called [@appland/cli](https://www.npmjs.com/package/@appland/cli), which does most of the heavy lifting of processing AppMap data.
|
6
|
+
|
7
|
+
# How it works
|
8
|
+
|
9
|
+
The Rake tasks `depends:modified` and `depends:diff` require Node.js and the NPM package `@appland/cli`.
|
10
|
+
|
11
|
+
Each task looks at which source files have been locally modified. The difference between `depends:modified` and `depends:diff` is what source files are considered modified; this difference is described in detail in the next section. The Rake task then scans the AppMaps to figure out which ones are out-of-date with respect to the source files. This is possible since each AppMap contains a `classMap`, which lists all the source files that are included in the recording. Each AppMap has a `source_location` field, which identifies the test case file that was run to generate the AppMap. `depends` prints the list of test case files to standard out.
|
12
|
+
|
13
|
+
The `depends:modified` task looks at which source files have been locally modified. This task is used in local development. Run it as you work with the code.
|
14
|
+
|
15
|
+
The `depends:diff` task looks at source files that are modified relative to a base `git` branch. This task is used in CI. Run it before running the full test suite, so that the tests which are most likely to fail will be run first. If any of the tests in this batch fail, fail the build until the developer fixes the tests, then run the full test suite.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
group :development, :test do
|
23
|
+
gem 'appmap_depends'
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
```sh-session
|
30
|
+
$ bundle install
|
31
|
+
```
|
32
|
+
|
33
|
+
# Usage
|
34
|
+
|
35
|
+
## Defining the Rake tasks
|
36
|
+
|
37
|
+
You need to define the Rake tasks. In Rails, this is done by creating a file like `lib/tasks/appmap.rake`.
|
38
|
+
|
39
|
+
In the file, check if `appmap_depends` is loaded, and then configure the Rake tasks.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
namespace :appmap do
|
43
|
+
def depends_tasks
|
44
|
+
namespace :depends do
|
45
|
+
task :modified do
|
46
|
+
@appmap_modified_files = AppMap::Depends.modified
|
47
|
+
AppMap::Depends.report_list 'Out of date', @appmap_modified_files
|
48
|
+
end
|
49
|
+
|
50
|
+
task :diff do
|
51
|
+
@appmap_modified_files = AppMap::Depends.diff(base: BASE_BRANCH)
|
52
|
+
AppMap::Depends.report_list 'Out of date', @appmap_modified_files
|
53
|
+
end
|
54
|
+
|
55
|
+
task :test_file_report do
|
56
|
+
@appmap_test_file_report = AppMap::Depends.inspect_test_files
|
57
|
+
@appmap_test_file_report.report
|
58
|
+
end
|
59
|
+
|
60
|
+
def run_minitest(test_files)
|
61
|
+
raise "RAILS_ENV must be 'test'" unless Rails.env.test?
|
62
|
+
$LOAD_PATH << 'test'
|
63
|
+
test_files.each do |test_file|
|
64
|
+
load test_file
|
65
|
+
end
|
66
|
+
$ARGV.replace []
|
67
|
+
Minitest.autorun
|
68
|
+
end
|
69
|
+
|
70
|
+
run_rspec(test_files)
|
71
|
+
system({ 'RAILS_ENV' => 'test', 'APPMAP' => 'true' }, "bundle exec rspec --format Fuubar #{test_files.map(&:shellescape).join(' ')}")
|
72
|
+
end
|
73
|
+
|
74
|
+
task :update_appmaps do
|
75
|
+
@appmap_test_file_report.clean_appmaps
|
76
|
+
|
77
|
+
@appmap_modified_files += @appmap_test_file_report.modified_files
|
78
|
+
|
79
|
+
if @appmap_modified_files.blank?
|
80
|
+
warn 'AppMaps are up to date'
|
81
|
+
next
|
82
|
+
end
|
83
|
+
|
84
|
+
AppMap::Depends.run_tests(@appmap_modified_files) do |test_files|
|
85
|
+
warn 'To generate AppMaps, uncomment run_minitest or run_rspec as appropriate for your project'
|
86
|
+
# run_minitest(test_files)
|
87
|
+
# run_rspec(test_files)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
desc 'Bring AppMaps up to date with local file modifications, and updated derived data such as Swagger files'
|
93
|
+
task :modified => [ :'depends:modified', :'depends:test_file_report', :'depends:update_appmaps', :swagger ]
|
94
|
+
|
95
|
+
desc 'Bring AppMaps up to date with file modifications relative to the base branch'
|
96
|
+
task :diff, [ :base ] => [ :'depends:diff', :'depends:update_appmaps', :swagger, :'swagger:uptodate' ]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if %w[test development].member?(Rails.env)
|
101
|
+
depends_tasks
|
102
|
+
|
103
|
+
desc 'Bring AppMaps up to date with local file modifications, and updated derived data such as Swagger files'
|
104
|
+
task :appmap => :'appmap:depends:modified'
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
## Running in CI
|
109
|
+
|
110
|
+
In the CI environment, run the `appmap:depends:diff` task to compute the list of changed test files, and then
|
111
|
+
run those tests directly using the test command.
|
112
|
+
|
113
|
+
### RSpec
|
114
|
+
|
115
|
+
This is easy with RSpec, just pipe the modified files to the `rspec` command:
|
116
|
+
|
117
|
+
```sh-session
|
118
|
+
$ bundle exec rake appmap:depends:diff | tee /dev/tty | xargs env APPMAP=true bundle exec rspec
|
119
|
+
```
|
120
|
+
|
121
|
+
### Minitest
|
122
|
+
|
123
|
+
Minitest doesn't have a "run the tests" command that's analagous to `rspec`. You can define a Rask task which computes the changed test files and then uses the
|
124
|
+
Rake task `Rails::TestUnit::Runner` to run the tests.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
namespace :appmap
|
128
|
+
if %w[test development].member?(Rails.env)
|
129
|
+
desc 'Run minitest tests that are modified relative to the base branch'
|
130
|
+
task :'test:diff' => :'test:prepare' do
|
131
|
+
task = AppMap::Depends::DiffTask.new
|
132
|
+
# This line is only needed if the base is not 'remotes/origin/main' or 'remotes/origin/master'
|
133
|
+
task.base = BASE_BRANCH
|
134
|
+
files = task.files
|
135
|
+
if Rake.verbose == true
|
136
|
+
warn 'Out of date tests:'
|
137
|
+
warn files.join(' ')
|
138
|
+
end
|
139
|
+
$: << "test"
|
140
|
+
Rails::TestUnit::Runner.rake_run(files)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
## Development
|
147
|
+
|
148
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
149
|
+
|
150
|
+
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).
|
151
|
+
|
152
|
+
## Contributing
|
153
|
+
|
154
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/applandinc/appmap_depends-ruby.
|
155
|
+
|
156
|
+
|
157
|
+
## License
|
158
|
+
|
159
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'appmap/depends/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "appmap_depends"
|
9
|
+
spec.version = AppMap::Depends::VERSION
|
10
|
+
spec.authors = ['Kevin Gilpin']
|
11
|
+
spec.email = ['kgilpin@gmail.com']
|
12
|
+
|
13
|
+
spec.summary = %q{Provides Rake tasks to compute code dependencies based on AppMap data}
|
14
|
+
spec.homepage = 'https://github.com/applandinc/appmap_depends-ruby'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
17
|
+
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
end
|
23
|
+
spec.bindir = "exe"
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.add_dependency 'rake'
|
28
|
+
spec.add_dependency 'activesupport'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'minitest'
|
31
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "appmap_depends"
|
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__)
|
data/bin/setup
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
module Depends
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def modified(appmap_dir: DEFAULT_APPMAP_DIR, base_dir: nil)
|
8
|
+
depends = AppMap::Depends::AppMapJSDepends.new(appmap_dir)
|
9
|
+
depends.base_dir = base_dir if base_dir
|
10
|
+
test_files = depends.depends
|
11
|
+
|
12
|
+
Set.new prune_directory_prefix(test_files)
|
13
|
+
end
|
14
|
+
|
15
|
+
def diff(appmap_dir: DEFAULT_APPMAP_DIR, base_dir: nil, base_branches: DEFAULT_BASE_BRANCHES, base: nil, head: nil)
|
16
|
+
diff = AppMap::Depends::GitDiff.new(base_branches: base_branches, base: base, head: head)
|
17
|
+
modified_files = diff.modified_files
|
18
|
+
|
19
|
+
depends = AppMap::Depends::AppMapJSDepends.new(appmap_dir)
|
20
|
+
depends.base_dir = base_dir if base_dir
|
21
|
+
test_files = depends.depends(modified_files)
|
22
|
+
|
23
|
+
Set.new prune_directory_prefix(test_files)
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect_test_files(appmap_dir = DEFAULT_APPMAP_DIR, test_file_patterns = DEFAULT_TEST_FILE_PATTERNS)
|
27
|
+
inspector = AppMap::Depends::TestFileInspector.new(appmap_dir, test_file_patterns)
|
28
|
+
inspector.report
|
29
|
+
end
|
30
|
+
|
31
|
+
def report_list(title, files)
|
32
|
+
warn [ title, files.to_a.sort.join(' ') ].join(': ') unless files.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_tests(test_files, appmap_dir: DEFAULT_APPMAP_DIR, &block)
|
36
|
+
test_files = test_files.to_a.sort
|
37
|
+
warn "Running tests: #{test_files.join(' ')}"
|
38
|
+
|
39
|
+
yield test_files
|
40
|
+
|
41
|
+
system(%(./node_modules/@appland/cli/src/cli.js index --appmap-dir #{appmap_dir.shellescape}))
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def prune_directory_prefix(files)
|
47
|
+
Util.normalize_path(files)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
APPMAP_JS = './node_modules/@appland/appmap/src/cli.js'
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
module Depends
|
5
|
+
# Utilities for invoking the +@appland/appmap+ CLI.
|
6
|
+
module AppMapJS
|
7
|
+
APPMAP_JS = './node_modules/@appland/cli/src/cli.js'
|
8
|
+
|
9
|
+
def detect_nodejs
|
10
|
+
do_fail('node', 'please install NodeJS') unless system('node --version 2>&1 > /dev/null')
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def detect_appmap_js
|
15
|
+
do_fail(APPMAP_JS, 'please install @appland/cli from NPM') unless File.exists?(APPMAP_JS)
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
def index_appmaps(appmap_dir)
|
20
|
+
appmap_js_command [ 'index', '--appmap-dir', appmap_dir ]
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def do_fail(command, msg)
|
25
|
+
command = command.join(' ') if command.is_a?(Array)
|
26
|
+
warn [ command, msg ].join('; ') if Depends.verbose
|
27
|
+
raise CommandError.new(command, msg)
|
28
|
+
end
|
29
|
+
|
30
|
+
def appmap_js_command(command, options = {})
|
31
|
+
command.unshift << '--verbose' if Depends.verbose
|
32
|
+
command.unshift APPMAP_JS
|
33
|
+
|
34
|
+
warn command.join(' ') if Depends.verbose
|
35
|
+
stdout, stderr, status = Open3.capture3({ 'NODE_OPTIONS' => '--trace-warnings' }, *command, options)
|
36
|
+
stdout_msg = stdout.split("\n").map {|line| "stdout: #{line}"}.join("\n") unless stdout.blank?
|
37
|
+
stderr_msg = stderr.split("\n").map {|line| "stderr: #{line}"}.join("\n") unless stderr.blank?
|
38
|
+
if Depends.verbose
|
39
|
+
warn stdout_msg if stdout_msg
|
40
|
+
warn stderr_msg if stderr_msg
|
41
|
+
end
|
42
|
+
unless status.exitstatus == 0
|
43
|
+
raise CommandError.new(command, [ stdout_msg, stderr_msg ].compact.join("\n"))
|
44
|
+
end
|
45
|
+
[ stdout, stderr ]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'appmap/depends/appmap_js'
|
3
|
+
|
4
|
+
module AppMap
|
5
|
+
module Depends
|
6
|
+
# +Command+ wraps the Node +depends+ command.
|
7
|
+
class AppMapJSDepends
|
8
|
+
include AppMapJS
|
9
|
+
|
10
|
+
# Directory to scan for AppMaps.
|
11
|
+
attr_accessor :appmap_dir
|
12
|
+
# Directory name to prefix to the list of modified files which is provided to +depends+.
|
13
|
+
attr_accessor :base_dir
|
14
|
+
|
15
|
+
def initialize(appmap_dir)
|
16
|
+
@appmap_dir = appmap_dir
|
17
|
+
@base_dir = false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Ensures that all dependencies are available.
|
21
|
+
def validate
|
22
|
+
detect_nodejs
|
23
|
+
detect_appmap_js
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the source_location field of every AppMap that is "out of date" with respect to one of the
|
27
|
+
# +modified_files+.
|
28
|
+
def depends(modified_files = nil)
|
29
|
+
validate
|
30
|
+
|
31
|
+
index_appmaps(appmap_dir)
|
32
|
+
|
33
|
+
cmd = %w[depends --field source_location]
|
34
|
+
cmd += [ '--appmap-dir', appmap_dir ] if appmap_dir
|
35
|
+
cmd += [ '--base-dir', base_dir ] if base_dir
|
36
|
+
|
37
|
+
options = {}
|
38
|
+
if modified_files
|
39
|
+
cmd << '--stdin-files'
|
40
|
+
options[:stdin_data] = modified_files.map(&:shellescape).join("\n")
|
41
|
+
warn "Checking modified files: #{modified_files.join(' ')}" if Depends.verbose
|
42
|
+
end
|
43
|
+
|
44
|
+
stdout, = appmap_js_command cmd, options
|
45
|
+
stdout.split("\n")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module AppMap
|
2
|
+
module Depends
|
3
|
+
# Raised when a system / shell command fails.
|
4
|
+
class CommandError < StandardError
|
5
|
+
attr_reader :command, :msg
|
6
|
+
|
7
|
+
def initialize(command, msg = nil)
|
8
|
+
super [ "Command failed: #{command}", msg ].compact.join('; ')
|
9
|
+
|
10
|
+
@command = command
|
11
|
+
@msg = msg
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module AppMap
|
2
|
+
module Depends
|
3
|
+
class GitDiff
|
4
|
+
attr_reader :head, :base_branches
|
5
|
+
|
6
|
+
def initialize(base_branches: [], base: nil, head: nil)
|
7
|
+
@base_branches = base_branches
|
8
|
+
@base = base
|
9
|
+
@head = head
|
10
|
+
end
|
11
|
+
|
12
|
+
def base=(base)
|
13
|
+
@base = base
|
14
|
+
end
|
15
|
+
|
16
|
+
def base
|
17
|
+
return @base if @base
|
18
|
+
|
19
|
+
git_exists = -> { system('git --version 2>&1 > /dev/null') }
|
20
|
+
detect_branch = ->(branch) { `git branch -a`.split("\n").map(&:strip).member?(branch) }
|
21
|
+
detect_base = lambda do
|
22
|
+
return nil unless git_exists.()
|
23
|
+
|
24
|
+
@base_branches.find(&detect_branch)
|
25
|
+
end
|
26
|
+
@base = detect_base.()
|
27
|
+
raise "Unable to detect base branch. Specify it explicitly as a task argument." unless @base
|
28
|
+
@base
|
29
|
+
end
|
30
|
+
|
31
|
+
def modified_files
|
32
|
+
warn "Using base #{base.inspect}" if Depends.verbose
|
33
|
+
warn "Using head #{head.inspect}" if head && Depends.verbose
|
34
|
+
|
35
|
+
branches = [ head, base ].compact
|
36
|
+
diff_cmd = [ 'git', 'diff', '--name-only', branches.join('..') ]
|
37
|
+
|
38
|
+
if Depends.verbose
|
39
|
+
warn diff_cmd.join(' ')
|
40
|
+
warn "Files modified #{head ? 'in ' + head : 'locally'} compared to #{base}:"
|
41
|
+
end
|
42
|
+
|
43
|
+
stdout, stderr, status = Open3.capture3(*diff_cmd)
|
44
|
+
if status.exitstatus != 0
|
45
|
+
warn stdout
|
46
|
+
warn stderr
|
47
|
+
raise CommandError.new(diff_cmd)
|
48
|
+
end
|
49
|
+
stdout.split("\n")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
module Depends
|
5
|
+
class TestFileInspector
|
6
|
+
TestReport = Struct.new(:metadata_files, :added, :removed, :changed, :failed) do
|
7
|
+
private_methods :metadata_files
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
report = []
|
11
|
+
report << "Added test files : #{added.to_a.join(' ')}" unless added.empty?
|
12
|
+
report << "Removed test files : #{removed.to_a.join(' ')}" unless removed.empty?
|
13
|
+
report << "Changed test files : #{changed.to_a.join(' ')}" unless changed.empty?
|
14
|
+
report << "Failed test files : #{failed.to_a.join(' ')}" unless failed.empty?
|
15
|
+
report.compact.join("\n")
|
16
|
+
end
|
17
|
+
|
18
|
+
def report
|
19
|
+
warn to_s unless empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def empty?
|
23
|
+
[ added, removed, changed, failed ].all?(&:empty?)
|
24
|
+
end
|
25
|
+
|
26
|
+
def modified_files
|
27
|
+
added + changed + failed
|
28
|
+
end
|
29
|
+
|
30
|
+
# Delete AppMaps which depend on test cases that have been deleted.
|
31
|
+
def clean_appmaps
|
32
|
+
return if removed.empty?
|
33
|
+
|
34
|
+
delete_appmap = lambda do |appmap_path|
|
35
|
+
FileUtils.rm_rf(appmap_path)
|
36
|
+
appmap_file_path = [ appmap_path, 'appmap.json' ].join('.')
|
37
|
+
File.unlink(appmap_file_path) if File.exists?(appmap_file_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
count = metadata_files.each_with_object(0) do |metadata_file, count|
|
41
|
+
metadata = JSON.parse(File.read(metadata_file))
|
42
|
+
source_location = Util.normalize_path(metadata['source_location'])
|
43
|
+
appmap_path = File.join(metadata_file.split('/')[0...-1])
|
44
|
+
|
45
|
+
if removed.member?(source_location)
|
46
|
+
delete_appmap.(appmap_path)
|
47
|
+
count += 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
count
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :test_dir
|
55
|
+
attr_reader :test_file_patterns
|
56
|
+
|
57
|
+
def initialize(test_dir, test_file_patterns)
|
58
|
+
@test_dir = test_dir
|
59
|
+
@test_file_patterns = test_file_patterns
|
60
|
+
end
|
61
|
+
|
62
|
+
def report
|
63
|
+
metadata_files = Dir.glob(File.join(test_dir, '**', 'metadata.json'))
|
64
|
+
source_locations = Set.new
|
65
|
+
changed_test_files = Set.new
|
66
|
+
failed_test_files = Set.new
|
67
|
+
metadata_files.each do |metadata_file|
|
68
|
+
metadata = JSON.parse(File.read(metadata_file))
|
69
|
+
appmap_path = File.join(metadata_file.split('/')[0...-1])
|
70
|
+
|
71
|
+
appmap_mtime = File.read(File.join(appmap_path, 'mtime')).to_i
|
72
|
+
source_location = Util.normalize_path(metadata['source_location'])
|
73
|
+
test_status = metadata['test_status']
|
74
|
+
source_location_mtime = (File.stat(source_location).mtime.to_f * 1000).to_i rescue nil
|
75
|
+
|
76
|
+
raise "Metadata #{metadata_file} does not contain source_location" unless source_location
|
77
|
+
raise "Metadata #{metadata_file} does not contain test_status" unless test_status
|
78
|
+
|
79
|
+
source_locations << source_location
|
80
|
+
if source_location_mtime
|
81
|
+
changed_test_files << source_location if source_location_mtime > appmap_mtime
|
82
|
+
failed_test_files << source_location unless test_status == 'succeeded'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
test_files = Set.new(test_file_patterns.map(&Dir.method(:glob)).flatten)
|
87
|
+
new_test_files = test_files - source_locations
|
88
|
+
obsolete_test_files = source_locations - test_files
|
89
|
+
|
90
|
+
TestReport.new(metadata_files, new_test_files, obsolete_test_files, changed_test_files, failed_test_files)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module AppMap
|
2
|
+
module Depends
|
3
|
+
module Util
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def normalize_path(path, pwd: Dir.pwd)
|
7
|
+
normalize_path_fn(pwd).(path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def normalize_paths(paths, pwd: Dir.pwd)
|
11
|
+
paths.map(&normalize_path_fn(pwd))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def normalize_path_fn(pwd)
|
17
|
+
lambda do |path|
|
18
|
+
path = path[pwd.length + 1..-1] if path.index(pwd) == 0
|
19
|
+
path.split(':')[0]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'appmap/depends/version'
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
module Depends
|
5
|
+
# Default directory to scan for appmap.s
|
6
|
+
DEFAULT_APPMAP_DIR = File.join('tmp', 'appmap')
|
7
|
+
# Default file to write Rake task results.
|
8
|
+
DEFAULT_OUTPUT_FILE = File.join('tmp', 'appmap_depends.txt')
|
9
|
+
# Default base branches which will be checked for existance.
|
10
|
+
DEFAULT_BASE_BRANCHES = %w[remotes/origin/main remotes/origin/master].freeze
|
11
|
+
# Default pattern to enumerate test cases.
|
12
|
+
DEFAULT_TEST_FILE_PATTERNS = [ 'spec/**/*_spec.rb', 'test/**/*_test.rb' ].freeze
|
13
|
+
|
14
|
+
def self.verbose(arg = nil)
|
15
|
+
@verbose = arg if arg
|
16
|
+
@verbose
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def rake_defined?
|
22
|
+
require 'rake'
|
23
|
+
true
|
24
|
+
rescue LoadError
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'appmap/depends/util'
|
29
|
+
require 'appmap/depends/command_error'
|
30
|
+
require 'appmap/depends/git_diff'
|
31
|
+
require 'appmap/depends/appmap_js_depends'
|
32
|
+
require 'appmap/depends/test_file_inspector'
|
33
|
+
require 'appmap/depends/api'
|
34
|
+
|
35
|
+
if rake_defined?
|
36
|
+
AppMap::Depends.verbose(Rake.verbose == true)
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: appmap_depends
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kevin Gilpin
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-05-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- kgilpin@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- CHANGELOG.md
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- appmap_depends.gemspec
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- lib/appmap/depends/api.rb
|
73
|
+
- lib/appmap/depends/appmap_js.rb
|
74
|
+
- lib/appmap/depends/appmap_js_depends.rb
|
75
|
+
- lib/appmap/depends/command_error.rb
|
76
|
+
- lib/appmap/depends/git_diff.rb
|
77
|
+
- lib/appmap/depends/test_file_inspector.rb
|
78
|
+
- lib/appmap/depends/util.rb
|
79
|
+
- lib/appmap/depends/version.rb
|
80
|
+
- lib/appmap_depends.rb
|
81
|
+
homepage: https://github.com/applandinc/appmap_depends-ruby
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 2.3.0
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubygems_version: 3.0.3
|
101
|
+
signing_key:
|
102
|
+
specification_version: 4
|
103
|
+
summary: Provides Rake tasks to compute code dependencies based on AppMap data
|
104
|
+
test_files: []
|