ttnt 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/Genki-S/ttnt.svg?branch=master)](https://travis-ci.org/Genki-S/ttnt)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/Genki-S/ttnt/badge.svg?branch=master)](https://coveralls.io/r/Genki-S/ttnt?branch=master)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/Genki-S/ttnt/badges/gpa.svg)](https://codeclimate.com/github/Genki-S/ttnt)
|
6
|
+
[![Dependency Status](https://gemnasium.com/Genki-S/ttnt.svg)](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
|