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 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)