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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d71f52f7c676f37872c83efc2a834699fa0b0651
4
- data.tar.gz: e2fc54d245e45e14677701c66bc114a0ee881a90
3
+ metadata.gz: 9947cd4256b1b4745ec7f4d215aa9c690fd48239
4
+ data.tar.gz: 787357a06a7c10ec0bffecef39f835991ebf04d5
5
5
  SHA512:
6
- metadata.gz: 7dd2fb1c983347930da189aa08f969ab9167c1b5bda8155def7d95b84bb96afdf0d130cbe2b5a1ca357cc515696eb37bfa513a92c983af654e3448be4ed9e769
7
- data.tar.gz: a551fe32c109fc43f47a875416bbf81c7e8fac8607eae68efa3928b314aee602c4d0fe0f5d257f06e8e03d41f2dfca2be433df4584314187f2439f0ae56c9e7d
6
+ metadata.gz: 79cb9265651ac4148288f8c74cebb20e360323818fce52fb96089dc9ef0ec4478cb9f17b1fad60946adf62f6c99276e3753614c3d8daa31f686bc13c079190ae
7
+ data.tar.gz: 2f7182fe104b563b933af1c28c51130ff87f6338f8d002d1053d028528e3c5a6eadb4a2bd720686edf0b2663008ffc2c3a74cf6f285c74d1bcd77130992c8ccb
data/.travis.yml CHANGED
@@ -1,4 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.0
3
+ - 2.2.2
4
4
  before_install: gem install bundler -v 1.10.2
5
+ install:
6
+ - ./bin/setup
7
+ script:
8
+ - bundle exec rake test
@@ -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
@@ -0,0 +1,4 @@
1
+ --protected
2
+ --private
3
+ --list-undoc
4
+ --readme README.md
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in ttnt.gemspec
4
4
  gemspec
5
+
6
+ group :development do
7
+ gem "coveralls", require: false
8
+ end
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.1.
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
- This pre-computation is required to select tests later.
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
- $ cd /your/local/rails/rails
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
- $ git checkout TARGET_COMMIT
92
- $ ttnt BASE_COMMIT
104
+ $ rake ttnt:my_test_name:run
93
105
  ```
94
106
 
95
- This will assume test-to-code mapping is properly produced for the BASE\_COMMIT.
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
- 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.
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.test_files = FileList['test/**/*_test.rb']
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
@@ -5,3 +5,4 @@ IFS=$'\n\t'
5
5
  bundle install
6
6
 
7
7
  # Do any other automated setup that you need to do here
8
+ git submodule update --init
data/lib/ttnt.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  require "ttnt/version"
2
2
 
3
+ # Test This, Not That!
4
+ #
5
+ # See {file:README.md} for more details.
3
6
  module TTNT
4
7
  # Your code goes here...
5
8
  end
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
- sha = Rugged::Repository.discover('.').head.target_id
12
- mapping = TTNT::TestToCodeMapping.new(sha)
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
@@ -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
@@ -1,40 +1,96 @@
1
1
  require 'set'
2
2
  require 'rugged'
3
- require_relative './test_to_code_mapping'
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
- def select_tests
14
- tests = Set.new
15
- mapping = TTNT::TestToCodeMapping.new(@base_obj.oid)
16
- # TODO: if mapping is not found (ttnt-anchor has not been run)
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
- diff = @base_obj.diff(@target_obj)
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
- patch.each_hunk do |hunk|
22
- # TODO: think if this selection covers all possibilities
23
- hunk.each_line do |line|
24
- case line.line_origin
25
- when :addition
26
- # FIXME: new_lineno is suspicious
27
- # (what if hunk1 adds 100 lines and hunk2 add 1 line?)
28
- tests += mapping.get_tests(file: file, lineno: line.new_lineno)
29
- when :deletion
30
- tests += mapping.get_tests(file: file, lineno: line.old_lineno)
31
- else
32
- # do nothing
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
- tests.delete(nil)
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
- DIRECTORY_SEPARATOR_PLACEHOLDER = '=+='.freeze
13
+ STORAGE_SECTION = 'mapping'
14
+
15
+ attr_reader :mapping
10
16
 
11
- def initialize(sha)
12
- @sha = sha
13
- @repo = Rugged::Repository.discover('.')
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 = normalize_path(select_project_files(spectra_from_coverage(coverage)))
19
- save_spectra(test: test, spectra: spectra)
34
+ spectra = normalize_paths(select_project_files(spectra_from_coverage(coverage)))
35
+ @mapping[test] = spectra
20
36
  end
21
37
 
22
- def read_spectra(test:)
23
- if File.exists?(spectra_file(test: test))
24
- JSON.parse(File.read(spectra_file(test: test)))
25
- else
26
- {}
27
- end
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
- all_tests.each do |test|
33
- spectra = read_spectra(test: test)
55
+ @mapping.each do |test, spectra|
34
56
  lines = spectra[file]
35
57
  next unless lines
36
- n = lines.bsearch { |x| x >= lineno }
37
- if n == lineno
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
- def normalize_path(spectra)
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
@@ -1,3 +1,3 @@
1
1
  module TTNT
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
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.2
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-06-07 00:00:00.000000000 Z
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
data/exe/ttnt-anchor DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- gem_root = File.expand_path('../lib', File.dirname(File.expand_path(__FILE__)))
4
- argv = ARGV
5
- argv.unshift('ruby', '-I', gem_root, '-r', 'ttnt/anchor')
6
-
7
- exec(*argv)