ttnt 0.0.2 → 0.0.3
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/.travis.yml +5 -1
- data/.ttnt/commit_obj.txt +1 -0
- data/.ttnt/test_to_code_mapping.json +1 -0
- data/.yardopts +4 -0
- data/Gemfile +4 -0
- data/README.md +47 -25
- data/Rakefile +4 -1
- data/bin/setup +1 -0
- data/lib/ttnt.rb +3 -0
- data/lib/ttnt/anchor.rb +9 -2
- data/lib/ttnt/metadata.rb +31 -0
- data/lib/ttnt/storage.rb +63 -0
- data/lib/ttnt/test_selector.rb +80 -24
- data/lib/ttnt/test_to_code_mapping.rb +59 -46
- data/lib/ttnt/testtask.rb +119 -0
- data/lib/ttnt/version.rb +1 -1
- data/ttnt.gemspec +5 -0
- metadata +80 -7
- data/exe/ttnt +0 -28
- data/exe/ttnt-anchor +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9947cd4256b1b4745ec7f4d215aa9c690fd48239
|
4
|
+
data.tar.gz: 787357a06a7c10ec0bffecef39f835991ebf04d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79cb9265651ac4148288f8c74cebb20e360323818fce52fb96089dc9ef0ec4478cb9f17b1fad60946adf62f6c99276e3753614c3d8daa31f686bc13c079190ae
|
7
|
+
data.tar.gz: 2f7182fe104b563b933af1c28c51130ff87f6338f8d002d1053d028528e3c5a6eadb4a2bd720686edf0b2663008ffc2c3a74cf6f285c74d1bcd77130992c8ccb
|
data/.travis.yml
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
8b4bac9f164b6389d657f4c1de100dccfc245651
|
@@ -0,0 +1 @@
|
|
1
|
+
{"test/fixtures/repositories/fizzbuzz/test/buzz_test.rb":{"lib/ttnt/version.rb":[1,2],"test/fixtures/repositories/fizzbuzz/lib/fizzbuzz.rb":[1,2,4,6,7]},"test/fixtures/repositories/fizzbuzz/test/fizz_test.rb":{"lib/ttnt/version.rb":[1,2],"test/fixtures/repositories/fizzbuzz/lib/fizzbuzz.rb":[1,2,4,5]},"test/fixtures/repositories/fizzbuzz/test/fizzbuzz_test.rb":{"lib/ttnt/version.rb":[1,2],"test/fixtures/repositories/fizzbuzz/lib/fizzbuzz.rb":[1,2,3]},"test/fixtures/repositories/fizzbuzz/test/non_fizzbuzz_test.rb":{"lib/ttnt/version.rb":[1,2],"test/fixtures/repositories/fizzbuzz/lib/fizzbuzz.rb":[1,2,4,6,9]},"test/lib/tasks_test.rb":{"lib/ttnt/version.rb":[1,2],"test/test_helper.rb":[1,2,4,6,7,8,10,14,18,20],"lib/ttnt.rb":[1,3],"lib/ttnt/tasks.rb":[1,2,3,4,5,8,9,10,12,13,16,17,18,19,35,36,56],"lib/ttnt/testtask.rb":[1,2,3,5,9,21,22,24,26],"lib/ttnt/test_selector.rb":[1,2,3,5,6,7,18]},"test/lib/test_selector_test.rb":{"lib/ttnt/version.rb":[1,2],"test/test_helper.rb":[1,2,4,6,7,8,10,11,14,15,18,20,21,22,23,24,25,26,28],"lib/ttnt.rb":[1,3],"lib/ttnt/test_selector.rb":[1,2,3,5,6,7,8,9,13,14,15,18,19,20,23,24,25,26,28,29,33,35,42]},"test/lib/test_to_code_mapping_test.rb":{"lib/ttnt/version.rb":[1,2],"test/test_helper.rb":[1,2,4,6,7,8,10,11,14,15,18,20,21,22,23,24,25,26,28],"lib/ttnt.rb":[1,3]},"test/lib/testtask_test.rb":{"lib/ttnt/version.rb":[1,2],"test/test_helper.rb":[1,2,4,6,7,8,10,14,18,20],"lib/ttnt.rb":[1,3],"lib/ttnt/testtask.rb":[1,2,3,5,6,9,21,22,24,26,27,28,29,33,34]},"test/ttnt_test.rb":{"lib/ttnt/version.rb":[1,2],"test/test_helper.rb":[1,2,4,6,7,8,10,14,18,20],"lib/ttnt.rb":[1,3]}}
|
data/.yardopts
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# TTNT: Test This, Not That!
|
2
2
|
|
3
|
+
[](https://travis-ci.org/Genki-S/ttnt)
|
4
|
+
[](https://coveralls.io/r/Genki-S/ttnt?branch=master)
|
5
|
+
[](https://codeclimate.com/github/Genki-S/ttnt)
|
6
|
+
[](https://gemnasium.com/Genki-S/ttnt)
|
7
|
+
|
3
8
|
Developing under [Google Summer of Code 2015](http://www.google-melange.com/gsoc/homepage/google/gsoc2015) with mentoring organization [Ruby on Rails](http://rubyonrails.org/).
|
4
9
|
|
5
10
|
Full proposal of this project is [here](https://github.com/Genki-S/gsoc2015/blob/master/proposal.md) (might be obsolete in some ways, read this README about this project itself rather than the proposal).
|
@@ -33,14 +38,6 @@ Currently, this program does:
|
|
33
38
|
- Generate test-to-code mapping for a given commit
|
34
39
|
- Given base commit and target commit, output test files you should run
|
35
40
|
|
36
|
-
Limitations:
|
37
|
-
|
38
|
-
- Test selection algorithm is not perfect yet (it may produce false-positives and false-negatives)
|
39
|
-
- Only supports git
|
40
|
-
- Only supports MiniTest
|
41
|
-
- Only select test files, not fine-grained test cases
|
42
|
-
- And a lot more!
|
43
|
-
|
44
41
|
## Roadmap
|
45
42
|
|
46
43
|
Roadmap is under construction.
|
@@ -61,38 +58,63 @@ Or install it yourself as:
|
|
61
58
|
|
62
59
|
$ gem install ttnt
|
63
60
|
|
61
|
+
### Define rake tasks
|
62
|
+
|
63
|
+
You can define TTNT rake tasks by following steps:
|
64
|
+
|
65
|
+
1. `require 'ttnt/testtask'`
|
66
|
+
2. Define `TTNT::TestTask` when defining `Rake::TestTask`
|
67
|
+
|
68
|
+
Your `Rakefile` will look like this:
|
69
|
+
|
70
|
+
```
|
71
|
+
require 'rake/testtask'
|
72
|
+
require 'ttnt/testtask'
|
73
|
+
|
74
|
+
Rake::TestTask.new { |t|
|
75
|
+
t.name = 'my_test_name'
|
76
|
+
t.libs << 'test'
|
77
|
+
t.pattern = 'test/**/*_test.rb'
|
78
|
+
TTNT::TestTask.new(t)
|
79
|
+
}
|
80
|
+
```
|
81
|
+
|
82
|
+
This will define 2 rake tasks `ttnt:my_test_name:anchor` and `ttnt:my_test_name:run` (portion of `my_test_name` depends on the name you specify for your `Rake::TestTask`).
|
83
|
+
Usage for those tasks are described later in this document.
|
84
|
+
|
64
85
|
## Requirements
|
65
86
|
|
66
|
-
Developed and only tested under ruby version 2.2.
|
87
|
+
Developed and only tested under ruby version 2.2.2.
|
67
88
|
|
68
89
|
## Usage
|
69
90
|
|
70
91
|
### Produce test-to-code mapping for a given commit
|
71
92
|
|
72
|
-
|
73
|
-
|
74
|
-
Basically, you can substitute `ruby` command with `ttnt-anchor` command.
|
75
|
-
For example, if you want to produce test-to-code mapping for `test/mail_layout_test.rb` in `ActionMailer` of rails/rails:
|
93
|
+
If you defined TTNT rake task as described above, you can run following command to produce test-to-code mapping:
|
76
94
|
|
77
95
|
```
|
78
|
-
$
|
79
|
-
$ git checkout BASE_COMMIT
|
80
|
-
$ cd actionmailer
|
81
|
-
$ ttnt-anchor -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout
|
96
|
+
$ rake ttnt:my_test_name:anchor
|
82
97
|
```
|
83
98
|
|
84
|
-
(this example is taken from ["Contributing to Ruby on Rails — Ruby on Rails Guides"](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#running-tests)).
|
85
|
-
|
86
|
-
This will produce test-to-code mapping for the test file `test/mail_layout_test.rb` under `.ttnt/BASE_COMMIT_SHA/test_to_code_mapping/TEST_FILE_NAME.json` which resides in your project base directory (the same repository in which `.git` directory resides).
|
87
|
-
|
88
99
|
### Select tests
|
89
100
|
|
101
|
+
If you defined TTNT rake task as described above, you can run following command to run selected tests.
|
102
|
+
|
90
103
|
```
|
91
|
-
$
|
92
|
-
$ ttnt BASE_COMMIT
|
104
|
+
$ rake ttnt:my_test_name:run
|
93
105
|
```
|
94
106
|
|
95
|
-
|
107
|
+
## Current Limitations
|
108
|
+
|
109
|
+
- Test selection algorithm is not perfect yet (it may produce false-positives and false-negatives)
|
110
|
+
- Only supports git
|
111
|
+
- Only supports MiniTest
|
112
|
+
- Only select test files, not fine-grained test cases
|
113
|
+
- And a lot more!
|
114
|
+
|
115
|
+
This gem can only produce test-to-code mapping "from a single test file to code lines executed"
|
116
|
+
(not fine-grained mapping "from a single test **case** to code lines executed").
|
117
|
+
This is due to the way Ruby's coverage library works. Details are covered in [my proposal](https://github.com/Genki-S/gsoc2015/blob/master/proposal.md#2-run-each-test-case-from-scratch-requiring-all-files-for-every-run).
|
96
118
|
|
97
119
|
## Development
|
98
120
|
|
@@ -104,7 +126,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
104
126
|
|
105
127
|
Bug reports and pull requests are welcome on GitHub at https://github.com/Genki-S/ttnt. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
106
128
|
|
107
|
-
|
129
|
+
I really :heart: getting interesting ideas, so please don't hesitate to [open a issue](https://github.com/Genki-S/ttnt/issues/new) to share your ideas for me! Any comment will be valuable especially in this early development stage. I am collecting interesting ideas which I cannot start working on soon [here at Trello](https://trello.com/b/z232DXnq/ttnt).
|
108
130
|
|
109
131
|
## License
|
110
132
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rake/testtask"
|
3
|
+
require "ttnt/testtask"
|
3
4
|
|
4
5
|
Rake::TestTask.new(:test) do |t|
|
5
6
|
t.libs << "test"
|
6
7
|
t.libs << "lib"
|
7
|
-
t.
|
8
|
+
t.warning = true
|
9
|
+
t.test_files = FileList['test/**/*_test.rb'] - FileList['test/fixtures/**/*_test.rb']
|
10
|
+
TTNT::TestTask.new(t)
|
8
11
|
end
|
9
12
|
|
10
13
|
task :default => :test
|
data/bin/setup
CHANGED
data/lib/ttnt.rb
CHANGED
data/lib/ttnt/anchor.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'coverage'
|
2
2
|
require 'ttnt/test_to_code_mapping'
|
3
|
+
require 'ttnt/metadata'
|
3
4
|
require 'rugged'
|
4
5
|
|
5
6
|
test_file = $0
|
@@ -8,7 +9,13 @@ Coverage.start
|
|
8
9
|
|
9
10
|
at_exit do
|
10
11
|
# Use current HEAD
|
11
|
-
|
12
|
-
|
12
|
+
repo = Rugged::Repository.discover('.')
|
13
|
+
sha = repo.head.target_id
|
14
|
+
mapping = TTNT::TestToCodeMapping.new(repo)
|
13
15
|
mapping.append_from_coverage(test_file, Coverage.result)
|
16
|
+
mapping.write!
|
17
|
+
|
18
|
+
metadata = TTNT::MetaData.new(repo)
|
19
|
+
metadata['anchored_commit'] = sha
|
20
|
+
metadata.write!
|
14
21
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'ttnt/storage'
|
2
|
+
|
3
|
+
module TTNT
|
4
|
+
class MetaData
|
5
|
+
STORAGE_SECTION = 'meta'
|
6
|
+
|
7
|
+
# @param repo [Rugged::Repository]
|
8
|
+
# @param sha [String] sha of commit from which metadata is read from.
|
9
|
+
# nil means to read from current working tree. see {Storage} for more.
|
10
|
+
def initialize(repo, sha = nil)
|
11
|
+
@storage = Storage.new(repo, sha)
|
12
|
+
read!
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](name)
|
16
|
+
@data[name]
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(name, value)
|
20
|
+
@data[name] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def read!
|
24
|
+
@data = @storage.read(STORAGE_SECTION)
|
25
|
+
end
|
26
|
+
|
27
|
+
def write!
|
28
|
+
@storage.write!(STORAGE_SECTION, @data)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/ttnt/storage.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module TTNT
|
2
|
+
# A utility class to store TTNT data such as test-to-code mapping and metadata.
|
3
|
+
class Storage
|
4
|
+
# Initialize the storage from given repo and sha. This reads contents from
|
5
|
+
# a file `.ttnt`. When sha is not nil, contents of the file on that commit
|
6
|
+
# is read. Data can be written only when sha is nil (written to current
|
7
|
+
# working tree).
|
8
|
+
#
|
9
|
+
# @param repo [Rugged::Repository]
|
10
|
+
# @param sha [String] sha of the commit from which data should be read.
|
11
|
+
# nil means reading from/writing to current working tree.
|
12
|
+
def initialize(repo, sha = nil)
|
13
|
+
@repo = repo
|
14
|
+
@sha = sha
|
15
|
+
end
|
16
|
+
|
17
|
+
# Read data in section from the storage.
|
18
|
+
#
|
19
|
+
# @param section [String]
|
20
|
+
# @return [Hash]
|
21
|
+
def read(section)
|
22
|
+
str = read_storage_content
|
23
|
+
if str.length > 0
|
24
|
+
JSON.parse(str)[section] || {}
|
25
|
+
else
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Write value to section in the storage.
|
31
|
+
# Locks the file so that concurrent write does not occur.
|
32
|
+
#
|
33
|
+
# @param section [String]
|
34
|
+
# @param value [Hash]
|
35
|
+
def write!(section, value)
|
36
|
+
raise 'Data cannot be written to the storage back in git history' unless @sha.nil?
|
37
|
+
File.open(filename, File::RDWR|File::CREAT, 0644) do |f|
|
38
|
+
f.flock(File::LOCK_EX)
|
39
|
+
str = f.read
|
40
|
+
data = str.length > 0 ? JSON.parse(str) : {}
|
41
|
+
data[section] = value
|
42
|
+
f.rewind
|
43
|
+
f.write(data.to_json)
|
44
|
+
f.flush
|
45
|
+
f.truncate(f.pos)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def filename
|
52
|
+
"#{@repo.workdir}/.ttnt"
|
53
|
+
end
|
54
|
+
|
55
|
+
def read_storage_content
|
56
|
+
if @sha
|
57
|
+
@repo.lookup(@repo.lookup(@sha).tree['.ttnt'][:oid]).content
|
58
|
+
else
|
59
|
+
File.exist?(filename) ? File.read(filename) : ''
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/ttnt/test_selector.rb
CHANGED
@@ -1,40 +1,96 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'rugged'
|
3
|
-
|
3
|
+
require 'ttnt/metadata'
|
4
|
+
require 'ttnt/test_to_code_mapping'
|
4
5
|
|
5
6
|
module TTNT
|
7
|
+
# Select tests using git information and {TestToCodeMapping}.
|
6
8
|
class TestSelector
|
7
|
-
def initialize(target_sha, base_sha)
|
8
|
-
@repo = Rugged::Repository.discover('.')
|
9
|
-
@target_obj = @repo.lookup(target_sha)
|
10
|
-
@base_obj = @repo.lookup(base_sha)
|
11
|
-
end
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
attr_reader :tests
|
11
|
+
|
12
|
+
# @param repo [Rugged::Reposiotry] repository of the project
|
13
|
+
# @param target_sha [String] sha of the target object
|
14
|
+
# (nil means to target current working tree)
|
15
|
+
# @param test_files [#include?] candidate test files
|
16
|
+
def initialize(repo, target_sha, test_files)
|
17
|
+
@repo = repo
|
18
|
+
@metadata = MetaData.new(repo, target_sha)
|
19
|
+
@target_obj = @repo.lookup(target_sha) if target_sha
|
20
|
+
|
21
|
+
# Base should be the commit `ttnt:anchor` has run on.
|
22
|
+
# NOT the one test-to-code mapping was commited to.
|
23
|
+
@base_obj = find_anchored_commit
|
24
|
+
|
25
|
+
@test_files = test_files
|
26
|
+
end
|
17
27
|
|
18
|
-
|
28
|
+
# Select tests using differences in anchored commit and target commit
|
29
|
+
# (or current working tree) and {TestToCodeMapping}.
|
30
|
+
#
|
31
|
+
# @return [Set] a set of tests that might be affected by changes in base_sha...target_sha
|
32
|
+
def select_tests!
|
33
|
+
# TODO: if test-to-code-mapping is not found (ttnt-anchor has not been run)
|
34
|
+
@tests ||= Set.new
|
35
|
+
diff = @target_obj ? @base_obj.diff(@target_obj) : @base_obj.diff_workdir
|
19
36
|
diff.each_patch do |patch|
|
20
37
|
file = patch.delta.old_file[:path]
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
38
|
+
if test_file?(file)
|
39
|
+
@tests << file
|
40
|
+
else
|
41
|
+
select_tests_from_patch(patch)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@tests.delete(nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def mapping
|
50
|
+
sha = @target_obj ? @target_obj.oid : @repo.head.target_id
|
51
|
+
@mapping ||= TTNT::TestToCodeMapping.new(@repo, sha)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Select tests which are affected by the change of given patch.
|
55
|
+
#
|
56
|
+
# @param patch [Rugged::Patch]
|
57
|
+
# @return [Set] set of selected tests
|
58
|
+
def select_tests_from_patch(patch)
|
59
|
+
target_lines = Set.new
|
60
|
+
file = patch.delta.old_file[:path]
|
61
|
+
prev_line = nil
|
62
|
+
patch.each_hunk do |hunk|
|
63
|
+
hunk.each_line do |line|
|
64
|
+
case line.line_origin
|
65
|
+
when :addition
|
66
|
+
if prev_line && !prev_line.addition?
|
67
|
+
target_lines << prev_line.old_lineno
|
68
|
+
elsif prev_line.nil?
|
69
|
+
target_lines << hunk.old_start
|
33
70
|
end
|
71
|
+
when :deletion
|
72
|
+
target_lines << line.old_lineno
|
34
73
|
end
|
74
|
+
|
75
|
+
prev_line = line
|
35
76
|
end
|
36
77
|
end
|
37
|
-
|
78
|
+
|
79
|
+
target_lines.each do |line|
|
80
|
+
@tests += mapping.get_tests(file: file, lineno: line)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Find the commit `rake ttnt:test:anchor` has been run on.
|
85
|
+
def find_anchored_commit
|
86
|
+
@repo.lookup(@metadata['anchored_commit'])
|
87
|
+
end
|
88
|
+
|
89
|
+
# Check if given file is a test file.
|
90
|
+
#
|
91
|
+
# @param filename [String]
|
92
|
+
def test_file?(filename)
|
93
|
+
@test_files.include?(filename)
|
38
94
|
end
|
39
95
|
end
|
40
96
|
end
|
@@ -1,40 +1,63 @@
|
|
1
|
+
require 'ttnt/storage'
|
1
2
|
require 'rugged'
|
2
3
|
require 'json'
|
3
|
-
|
4
|
-
# Terminologies:
|
5
|
-
# spectra: { filename => [line, numbers, executed], ... }
|
4
|
+
require 'set'
|
6
5
|
|
7
6
|
module TTNT
|
7
|
+
# Mapping from test file to executed code (i.e. coverage without execution count).
|
8
|
+
#
|
9
|
+
# Terminologies:
|
10
|
+
# spectra: { filename => [line, numbers, executed], ... }
|
11
|
+
# mapping: { test_file => spectra }
|
8
12
|
class TestToCodeMapping
|
9
|
-
|
13
|
+
STORAGE_SECTION = 'mapping'
|
14
|
+
|
15
|
+
attr_reader :mapping
|
10
16
|
|
11
|
-
|
12
|
-
|
13
|
-
|
17
|
+
# @param repo [Rugged::Reposiotry] repository to save test-to-code mapping
|
18
|
+
# (only repo.workdir is used to determine where to save the mapping file)
|
19
|
+
# @param sha [String] sha of commit from which mapping is read.
|
20
|
+
# nil means to read from current working tree. see {Storage} for more.
|
21
|
+
def initialize(repo, sha = nil)
|
22
|
+
@repo = repo
|
23
|
+
@storage = Storage.new(repo, sha)
|
24
|
+
read!
|
14
25
|
raise 'Not in a git repository' unless @repo
|
15
26
|
end
|
16
27
|
|
28
|
+
# Append the new mapping to test-to-code mapping file.
|
29
|
+
#
|
30
|
+
# @param test [String] test file for which the coverage data is produced
|
31
|
+
# @param coverage [Hash] coverage data generated using `Coverage.start` and `Coverage.result`
|
32
|
+
# @return [void]
|
17
33
|
def append_from_coverage(test, coverage)
|
18
|
-
spectra =
|
19
|
-
|
34
|
+
spectra = normalize_paths(select_project_files(spectra_from_coverage(coverage)))
|
35
|
+
@mapping[test] = spectra
|
20
36
|
end
|
21
37
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
38
|
+
# Read test-to-code mapping from storage.
|
39
|
+
def read!
|
40
|
+
@mapping = @storage.read(STORAGE_SECTION)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Write test-to-code mapping to storage.
|
44
|
+
def write!
|
45
|
+
@storage.write!(STORAGE_SECTION, @mapping)
|
28
46
|
end
|
29
47
|
|
48
|
+
# Get tests affected from change of file `file` at line number `lineno`
|
49
|
+
#
|
50
|
+
# @param file [String] file name which might have effects on some tests
|
51
|
+
# @param lineno [Integer] line number in the file which might have effects on some tests
|
52
|
+
# @return [Set] a set of test files which might be affected by the change in file at lineno
|
30
53
|
def get_tests(file:, lineno:)
|
31
54
|
tests = Set.new
|
32
|
-
|
33
|
-
spectra = read_spectra(test: test)
|
55
|
+
@mapping.each do |test, spectra|
|
34
56
|
lines = spectra[file]
|
35
57
|
next unless lines
|
36
|
-
|
37
|
-
|
58
|
+
topmost = lines.first
|
59
|
+
downmost = lines.last
|
60
|
+
if topmost <= lineno && lineno <= downmost
|
38
61
|
tests << test
|
39
62
|
end
|
40
63
|
end
|
@@ -43,22 +66,38 @@ module TTNT
|
|
43
66
|
|
44
67
|
private
|
45
68
|
|
69
|
+
# Convert absolute path to relative path from the project (git repository) root.
|
70
|
+
#
|
71
|
+
# @param file [String] file name (absolute path)
|
72
|
+
# @return [String] normalized file path
|
46
73
|
def normalized_path(file)
|
47
74
|
File.expand_path(file).sub(@repo.workdir, '')
|
48
75
|
end
|
49
76
|
|
50
|
-
|
77
|
+
# Normalize all file names in a spectra.
|
78
|
+
#
|
79
|
+
# @param spectra [Hash] spectra data
|
80
|
+
# @return [Hash] spectra whose keys (file names) are normalized
|
81
|
+
def normalize_paths(spectra)
|
51
82
|
spectra.map do |filename, lines|
|
52
83
|
[normalized_path(filename), lines]
|
53
84
|
end.to_h
|
54
85
|
end
|
55
86
|
|
87
|
+
# Filter out the files outside of the target project using file path.
|
88
|
+
#
|
89
|
+
# @param spectra [Hash] spectra data
|
90
|
+
# @return [Hash] spectra with only files inside the target project
|
56
91
|
def select_project_files(spectra)
|
57
92
|
spectra.select do |filename, lines|
|
58
93
|
filename.start_with?(@repo.workdir)
|
59
94
|
end
|
60
95
|
end
|
61
96
|
|
97
|
+
# Generate spectra data from Ruby coverage library's data
|
98
|
+
#
|
99
|
+
# @param cov [Hash] coverage data generated using `Coverage.result`
|
100
|
+
# @return [Hash] spectra data
|
62
101
|
def spectra_from_coverage(cov)
|
63
102
|
spectra = Hash.new { |h, k| h[k] = [] }
|
64
103
|
cov.each do |filename, executions|
|
@@ -69,31 +108,5 @@ module TTNT
|
|
69
108
|
end
|
70
109
|
spectra
|
71
110
|
end
|
72
|
-
|
73
|
-
def save_spectra(test:, spectra:)
|
74
|
-
dir = base_savedir
|
75
|
-
unless File.directory?(dir)
|
76
|
-
FileUtils.mkdir_p(dir)
|
77
|
-
end
|
78
|
-
File.write(spectra_file(test: test), spectra.to_json)
|
79
|
-
end
|
80
|
-
|
81
|
-
def base_savedir
|
82
|
-
"#{@repo.workdir}/.ttnt/#{@sha}/test_to_code_mapping"
|
83
|
-
end
|
84
|
-
|
85
|
-
def spectra_file(test:)
|
86
|
-
filename = normalized_path(test).gsub('/', DIRECTORY_SEPARATOR_PLACEHOLDER)
|
87
|
-
"#{base_savedir}/#{filename}.json"
|
88
|
-
end
|
89
|
-
|
90
|
-
def all_tests
|
91
|
-
Dir["#{base_savedir}/*.json"].map do |filename|
|
92
|
-
filename
|
93
|
-
.sub(/.*\//, '')
|
94
|
-
.sub(/\.json\Z/, '')
|
95
|
-
.gsub(DIRECTORY_SEPARATOR_PLACEHOLDER, '/')
|
96
|
-
end
|
97
|
-
end
|
98
111
|
end
|
99
112
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'rugged'
|
2
|
+
require 'rake'
|
3
|
+
require 'ttnt/test_selector'
|
4
|
+
|
5
|
+
module TTNT
|
6
|
+
# TTNT version of Rake::TestTask.
|
7
|
+
# Uses configuration from Rake::TestTask to minimize user configuration.
|
8
|
+
# Defines TTNT related rake tasks when instantiated.
|
9
|
+
class TestTask
|
10
|
+
include Rake::DSL
|
11
|
+
|
12
|
+
# An instance of `Rake::TestTask` passed when TTNT::TestTask is initialized
|
13
|
+
attr_accessor :rake_testtask
|
14
|
+
|
15
|
+
# Create an instance of TTNT::TestTask and define TTNT rake tasks.
|
16
|
+
#
|
17
|
+
# @param rake_testtask [Rake::TestTask] an instance of Rake::TestTask after user configuration is done
|
18
|
+
def initialize(rake_testtask)
|
19
|
+
@rake_testtask = rake_testtask
|
20
|
+
# Since test_files is not exposed in Rake::TestTask
|
21
|
+
@test_files = @rake_testtask.instance_variable_get('@test_files')
|
22
|
+
|
23
|
+
@anchor_description = 'Generate test-to-code mapping' + (@rake_testtask.name == :test ? '' : " for #{@rake_testtask.name}")
|
24
|
+
@run_description = 'Run selected tests' + (@rake_testtask.name == :test ? '' : "for #{@rake_testtask.name}")
|
25
|
+
define_tasks
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns array of test file names.
|
29
|
+
# Unlike Rake::TestTask#file_list, patterns are expanded.
|
30
|
+
def expanded_file_list
|
31
|
+
test_files = Rake::FileList[@rake_testtask.pattern].compact
|
32
|
+
test_files += @test_files.to_a if @test_files
|
33
|
+
test_files
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Git repository discovered from current directory
|
39
|
+
#
|
40
|
+
# @return [Rugged::Reposiotry]
|
41
|
+
def repo
|
42
|
+
@repo ||= Rugged::Repository.discover('.')
|
43
|
+
end
|
44
|
+
|
45
|
+
# Define TTNT tasks under namespace 'ttnt:TESTNAME'
|
46
|
+
#
|
47
|
+
# @return [void]
|
48
|
+
def define_tasks
|
49
|
+
# Task definitions are taken from Rake::TestTask
|
50
|
+
# https://github.com/ruby/rake/blob/e644af3/lib/rake/testtask.rb#L98-L112
|
51
|
+
namespace :ttnt do
|
52
|
+
namespace @rake_testtask.name do
|
53
|
+
define_run_task
|
54
|
+
define_anchor_task
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Define a task which runs only tests which might have affected from changes
|
60
|
+
# between anchored commit and TARGET_SHA.
|
61
|
+
#
|
62
|
+
# TARGET_SHA can be specified as an environment variable (defaults to HEAD).
|
63
|
+
#
|
64
|
+
# @return [void]
|
65
|
+
def define_run_task
|
66
|
+
desc @run_description
|
67
|
+
task 'run' do
|
68
|
+
target_sha = ENV['TARGET_SHA']
|
69
|
+
ts = TTNT::TestSelector.new(repo, target_sha, expanded_file_list)
|
70
|
+
tests = ts.select_tests!
|
71
|
+
if tests.empty?
|
72
|
+
STDERR.puts 'No test selected.'
|
73
|
+
else
|
74
|
+
args =
|
75
|
+
"#{@rake_testtask.ruby_opts_string} #{@rake_testtask.run_code} " +
|
76
|
+
"#{tests.to_a.join(' ')} #{@rake_testtask.option_list}"
|
77
|
+
run_ruby args
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Define a task which runs test files file by file, and generate and save
|
83
|
+
# test-to-code mapping.
|
84
|
+
#
|
85
|
+
# @return [void]
|
86
|
+
def define_anchor_task
|
87
|
+
desc @anchor_description
|
88
|
+
task 'anchor' do
|
89
|
+
# In order to make it possible to stop coverage services like Coveralls
|
90
|
+
# which interferes with ttnt/anchor because both use coverage library.
|
91
|
+
# See test/test_helper.rb
|
92
|
+
ENV['ANCHOR_TASK'] = '1'
|
93
|
+
|
94
|
+
Rake::FileUtilsExt.verbose(@rake_testtask.verbose) do
|
95
|
+
# Make it possible to require files in this gem
|
96
|
+
gem_root = File.expand_path('../..', __FILE__)
|
97
|
+
args =
|
98
|
+
"-I#{gem_root} -r ttnt/anchor " +
|
99
|
+
"#{@rake_testtask.ruby_opts_string}"
|
100
|
+
|
101
|
+
expanded_file_list.each do |test_file|
|
102
|
+
run_ruby "#{args} #{test_file}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Run ruby process with given args
|
109
|
+
#
|
110
|
+
# @param args [String] argument to pass to ruby
|
111
|
+
def run_ruby(args)
|
112
|
+
ruby "#{args}" do |ok, status|
|
113
|
+
if !ok && status.respond_to?(:signaled?) && status.signaled?
|
114
|
+
raise SignalException.new(status.termsig)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/ttnt/version.rb
CHANGED
data/ttnt.gemspec
CHANGED
@@ -33,9 +33,14 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_development_dependency "bundler", "~> 1.10"
|
34
34
|
spec.add_development_dependency "rake", "~> 10.0"
|
35
35
|
spec.add_development_dependency "minitest"
|
36
|
+
spec.add_development_dependency "coveralls"
|
37
|
+
spec.add_development_dependency "yard"
|
36
38
|
|
37
39
|
# Pry
|
38
40
|
spec.add_development_dependency "hirb"
|
39
41
|
spec.add_development_dependency "awesome_print"
|
40
42
|
spec.add_development_dependency "pry"
|
43
|
+
spec.add_development_dependency "pry-doc"
|
44
|
+
spec.add_development_dependency "pry-byebug"
|
45
|
+
spec.add_development_dependency "pry-rescue"
|
41
46
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ttnt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Genki Sugimoto
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rugged
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: coveralls
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: yard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: hirb
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,17 +150,60 @@ dependencies:
|
|
122
150
|
- - ">="
|
123
151
|
- !ruby/object:Gem::Version
|
124
152
|
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: pry-doc
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: pry-byebug
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: pry-rescue
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
125
195
|
description:
|
126
196
|
email:
|
127
197
|
- cfhoyuk.reccos.nelg@gmail.com
|
128
|
-
executables:
|
129
|
-
- ttnt
|
130
|
-
- ttnt-anchor
|
198
|
+
executables: []
|
131
199
|
extensions: []
|
132
200
|
extra_rdoc_files: []
|
133
201
|
files:
|
134
202
|
- ".gitignore"
|
135
203
|
- ".travis.yml"
|
204
|
+
- ".ttnt/commit_obj.txt"
|
205
|
+
- ".ttnt/test_to_code_mapping.json"
|
206
|
+
- ".yardopts"
|
136
207
|
- CODE_OF_CONDUCT.md
|
137
208
|
- Gemfile
|
138
209
|
- LICENSE.txt
|
@@ -140,12 +211,13 @@ files:
|
|
140
211
|
- Rakefile
|
141
212
|
- bin/console
|
142
213
|
- bin/setup
|
143
|
-
- exe/ttnt
|
144
|
-
- exe/ttnt-anchor
|
145
214
|
- lib/ttnt.rb
|
146
215
|
- lib/ttnt/anchor.rb
|
216
|
+
- lib/ttnt/metadata.rb
|
217
|
+
- lib/ttnt/storage.rb
|
147
218
|
- lib/ttnt/test_selector.rb
|
148
219
|
- lib/ttnt/test_to_code_mapping.rb
|
220
|
+
- lib/ttnt/testtask.rb
|
149
221
|
- lib/ttnt/version.rb
|
150
222
|
- ttnt.gemspec
|
151
223
|
homepage: http://github.com/Genki-S/ttnt
|
@@ -173,3 +245,4 @@ signing_key:
|
|
173
245
|
specification_version: 4
|
174
246
|
summary: Select test cases to run based on changes in committed code
|
175
247
|
test_files: []
|
248
|
+
has_rdoc:
|
data/exe/ttnt
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'rugged'
|
4
|
-
require_relative '../lib/ttnt/test_selector'
|
5
|
-
|
6
|
-
USAGE = %{
|
7
|
-
ttnt (Test This, Not That)
|
8
|
-
|
9
|
-
Usage:
|
10
|
-
ttnt BASE_SHA
|
11
|
-
|
12
|
-
What it does:
|
13
|
-
Output test files you should run based on changes between commits BASE_SHA and HEAD.
|
14
|
-
}
|
15
|
-
|
16
|
-
if ARGV.count != 1
|
17
|
-
puts USAGE
|
18
|
-
exit 1
|
19
|
-
end
|
20
|
-
|
21
|
-
repo = Rugged::Repository.discover('.')
|
22
|
-
ts = TTNT::TestSelector.new(repo.head.target_id, ARGV[0])
|
23
|
-
tests = ts.select_tests
|
24
|
-
if tests.empty?
|
25
|
-
STDERR.puts 'No test selected.'
|
26
|
-
else
|
27
|
-
puts tests.to_a.join("\n")
|
28
|
-
end
|