xing-backend-specdoc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/xing/specdoc/api-doccer.rb +63 -0
- data/lib/xing/specdoc/doc-family.rb +92 -0
- data/lib/xing/specdoc/document.rb +73 -0
- data/lib/xing/specdoc/module.rb +40 -0
- data/lib/xing/specdoc/patterner.rb +41 -0
- data/lib/xing/specdoc/winnower.rb +66 -0
- data/lib/xing/specdoc.rb +11 -0
- data/spec/document_spec.rb +125 -0
- data/spec/winnower_spec.rb +112 -0
- data/spec_help/file-sandbox.rb +164 -0
- data/spec_help/gem_test_suite.rb +17 -0
- data/spec_help/spec_helper.rb +10 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a4fb54b420e096821934f1179eb5c9f274f70deb
|
4
|
+
data.tar.gz: b0624e2586339eb47c2b6e18fc1165cb422dec75
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c70d8ad1026fa80d5d8545dfc76054ba26b530687e6455ee9f098d001c92d5148df66c7fd2f9755585362002a26167349dc7a73d85612e9c4af862e19e87bd0d
|
7
|
+
data.tar.gz: 98efef1c24410242f382363ec918a5b583db04a50aa17aa6584d978cf867848b7272e47938d0d16f3ab066757055c50154da7bee95b3f041f407556e05bce69f
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'xing/specdoc/patterner'
|
2
|
+
require 'xing/specdoc/document'
|
3
|
+
|
4
|
+
module Xing
|
5
|
+
module SpecDoc
|
6
|
+
class ApiDoccer
|
7
|
+
def self.patterner
|
8
|
+
@patterner ||= Patterner.new(Rails.application.routes)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(example, request, response, patterner=nil)
|
12
|
+
@example = example
|
13
|
+
@request = request
|
14
|
+
@response = response
|
15
|
+
@patterner = patterner || ApiDoccer.patterner
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :example, :request, :response, :patterner
|
19
|
+
|
20
|
+
def output_path
|
21
|
+
@output_path ||= File::join( SpecDoc.response_target_dir, filename )
|
22
|
+
end
|
23
|
+
|
24
|
+
def base_filename
|
25
|
+
@base_filename ||=
|
26
|
+
begin
|
27
|
+
pattern = patterner.build(request).sub(%r{^/},"").gsub(%{/:},":").gsub(%r{/},"-")
|
28
|
+
File::join(example.metadata[:doc_path],
|
29
|
+
"#{request.method}+#{response.status}@#{pattern}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def filename
|
34
|
+
base_filename + ".json"
|
35
|
+
end
|
36
|
+
|
37
|
+
def existing_body
|
38
|
+
JSON.parse(File.read(output_path))
|
39
|
+
end
|
40
|
+
|
41
|
+
def example_passed?
|
42
|
+
example.exception.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def document
|
46
|
+
@document ||= Document.new(output_path, response.body)
|
47
|
+
end
|
48
|
+
|
49
|
+
def significant_change?
|
50
|
+
document.different_from?(existing_body)
|
51
|
+
rescue Errno::ENOENT
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def store
|
56
|
+
if example_passed?
|
57
|
+
Xing::SpecDoc.store(self)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'xing/specdoc/document'
|
3
|
+
require 'xing/specdoc/winnower'
|
4
|
+
|
5
|
+
module Xing
|
6
|
+
module SpecDoc
|
7
|
+
class DocFamily
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
@docs = []
|
11
|
+
@index = 1
|
12
|
+
@out_stream = $stdout
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :name, :docs
|
16
|
+
attr_accessor :out_stream
|
17
|
+
|
18
|
+
def add(doc)
|
19
|
+
@docs << doc
|
20
|
+
end
|
21
|
+
|
22
|
+
def old_doc_glob
|
23
|
+
File::join(Xing::SpecDoc.response_target_dir, name) + "*"
|
24
|
+
end
|
25
|
+
|
26
|
+
def old_doc_paths
|
27
|
+
@old_doc_paths ||= dir_glob(old_doc_glob).find_all do |path|
|
28
|
+
re = /#{Regexp.escape(name)}-\d+\.json$/
|
29
|
+
path =~ re
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def old_docs
|
34
|
+
old_doc_paths.map do |path|
|
35
|
+
Document.new(path, File.read(path))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def record
|
40
|
+
ensure_target_dir
|
41
|
+
|
42
|
+
winnower = Winnower.new(docs, old_docs)
|
43
|
+
|
44
|
+
winnower.obsolete_paths.each do |path|
|
45
|
+
kill(path)
|
46
|
+
end
|
47
|
+
|
48
|
+
winnower.kept_new.each do |doc|
|
49
|
+
write(doc)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def next_output_path
|
54
|
+
loop do
|
55
|
+
candidate = File::join(Xing::SpecDoc.response_target_dir, "#{name}-#{@index}.json")
|
56
|
+
if exist?(candidate)
|
57
|
+
@index += 1
|
58
|
+
else
|
59
|
+
return candidate
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def ensure_target_dir
|
65
|
+
FileUtils.mkdir_p(File.dirname(File::join(Xing::SpecDoc.response_target_dir, name)))
|
66
|
+
end
|
67
|
+
|
68
|
+
def kill(path)
|
69
|
+
@out_stream.puts "Removing outdated JSON example at #{path}"
|
70
|
+
FileUtils::rm_f(path)
|
71
|
+
end
|
72
|
+
|
73
|
+
def write(doc)
|
74
|
+
path = next_output_path
|
75
|
+
@out_stream.puts "Writing new JSON example to #{path}"
|
76
|
+
File.write(path, doc.pretty_body)
|
77
|
+
end
|
78
|
+
|
79
|
+
def dir_glob(pattern)
|
80
|
+
Dir.glob(pattern)
|
81
|
+
end
|
82
|
+
|
83
|
+
def exist?(path)
|
84
|
+
File.exist?(path)
|
85
|
+
end
|
86
|
+
|
87
|
+
def read(path)
|
88
|
+
File.read(path)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'hashdiff'
|
2
|
+
|
3
|
+
module Xing
|
4
|
+
module SpecDoc
|
5
|
+
class Document
|
6
|
+
def initialize(path, contents)
|
7
|
+
@path = path
|
8
|
+
@contents = contents
|
9
|
+
end
|
10
|
+
attr_reader :path, :contents
|
11
|
+
|
12
|
+
def parsed_body
|
13
|
+
@parsed_body ||= JSON.parse(contents)
|
14
|
+
end
|
15
|
+
|
16
|
+
def pretty_body
|
17
|
+
@pretty_body ||= JSON.pretty_generate(parsed_body)+"\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
def difference_from(other)
|
21
|
+
diff(parsed_body, other)
|
22
|
+
end
|
23
|
+
|
24
|
+
def diff(one, other)
|
25
|
+
diff = HashDiff.diff(one, other)
|
26
|
+
|
27
|
+
|
28
|
+
by_change = diff.group_by {|change| change[0]}
|
29
|
+
|
30
|
+
adds = by_change.delete("+") || []
|
31
|
+
subs = by_change.delete("-") || []
|
32
|
+
mods = by_change.values.inject do |all, a_kind|
|
33
|
+
all + a_kind
|
34
|
+
end || []
|
35
|
+
|
36
|
+
arr_adds, other_adds = adds.partition do |add|
|
37
|
+
add[1] =~ /\[\d+\]$/
|
38
|
+
end
|
39
|
+
|
40
|
+
mods += (other_adds || [])
|
41
|
+
|
42
|
+
arr_adds.each do |add|
|
43
|
+
sub = subs.find{|sub| sub[1] == add[1]}
|
44
|
+
if sub.nil?
|
45
|
+
mods << add
|
46
|
+
else
|
47
|
+
subs.delete(sub)
|
48
|
+
new_diff = diff(add[2], sub[2])
|
49
|
+
mods += new_diff.map do |change|
|
50
|
+
[change[0], [add[1], change[1]].join(".")] + change[2..-1]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
mods += subs
|
56
|
+
|
57
|
+
mods
|
58
|
+
end
|
59
|
+
|
60
|
+
def significant_diff_item?(change)
|
61
|
+
return true if change[0] != "~"
|
62
|
+
return true if change[2].class != change[3].class
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
def different_from?(body)
|
67
|
+
difference_from(body).any? do |change|
|
68
|
+
significant_diff_item?(change)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'xing/specdoc/api-doccer'
|
2
|
+
require 'xing/specdoc/doc-family'
|
3
|
+
|
4
|
+
module Xing
|
5
|
+
module SpecDoc
|
6
|
+
class << self
|
7
|
+
def target_dir
|
8
|
+
RSpec.configuration.specdoc_target_directory
|
9
|
+
end
|
10
|
+
|
11
|
+
def response_target_dir
|
12
|
+
File.join(target_dir, "responses")
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_target_dir
|
16
|
+
File.join(target_dir, "requests")
|
17
|
+
end
|
18
|
+
|
19
|
+
def doc_hash
|
20
|
+
@doc_hash ||= Hash.new{|h,k| h[k]= DocFamily.new(k)}
|
21
|
+
end
|
22
|
+
|
23
|
+
def store(doc)
|
24
|
+
doc_hash[doc.base_filename].add(doc)
|
25
|
+
end
|
26
|
+
|
27
|
+
def record_docs
|
28
|
+
doc_hash.each_pair do |_, docs|
|
29
|
+
docs.record
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def included(group)
|
34
|
+
group.after(:each) do |example|
|
35
|
+
ApiDoccer.new(example, request, response).store
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Xing
|
2
|
+
module SpecDoc
|
3
|
+
class Patterner
|
4
|
+
def initialize(rails_routes, route_set=nil)
|
5
|
+
@rails_routes = rails_routes
|
6
|
+
@route_set = route_set
|
7
|
+
end
|
8
|
+
attr_reader :rails_routes
|
9
|
+
|
10
|
+
def route_map
|
11
|
+
@route_map ||=
|
12
|
+
begin
|
13
|
+
ad_routes_array = rails_routes.routes
|
14
|
+
rack_routes_array = rails_routes.set.instance_eval{ @routes }
|
15
|
+
Hash[ rack_routes_array.zip(ad_routes_array) ]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def route_set
|
20
|
+
@route_set ||= Rails.application.routes.router
|
21
|
+
end
|
22
|
+
|
23
|
+
def build(req)
|
24
|
+
route_set.recognize(req) do |route, matches, params|
|
25
|
+
rails_route = route_map[route]
|
26
|
+
|
27
|
+
path_spec = :unrecognized
|
28
|
+
segment_keys = {}
|
29
|
+
|
30
|
+
if route_map.has_key?(route)
|
31
|
+
rails_route = route_map[route]
|
32
|
+
path_spec = rails_route.path.spec.to_s
|
33
|
+
segment_keys = rails_route.segment_keys
|
34
|
+
return path_spec.sub(/\(\.:format\)/,"")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
|
2
|
+
module Xing
|
3
|
+
module SpecDoc
|
4
|
+
class Winnower
|
5
|
+
def initialize(incoming_docs, existing_docs)
|
6
|
+
@incoming_docs = incoming_docs
|
7
|
+
@existing_docs = existing_docs
|
8
|
+
|
9
|
+
reset
|
10
|
+
end
|
11
|
+
|
12
|
+
def kept_new
|
13
|
+
if @keepable.nil?
|
14
|
+
resolve
|
15
|
+
end
|
16
|
+
@keepable
|
17
|
+
end
|
18
|
+
|
19
|
+
def obsolete_paths
|
20
|
+
if @keepable.nil?
|
21
|
+
resolve
|
22
|
+
end
|
23
|
+
@obsolete_paths
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset
|
27
|
+
@obsolete_paths = []
|
28
|
+
@keepable = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def resolve
|
32
|
+
new_docs = significant_docs
|
33
|
+
|
34
|
+
@existing_docs.each do |doc|
|
35
|
+
new_docs = resolve_against_existing(doc, new_docs)
|
36
|
+
end
|
37
|
+
|
38
|
+
@keepable = new_docs
|
39
|
+
end
|
40
|
+
|
41
|
+
def significant_docs
|
42
|
+
@incoming_docs.reduce([]) do |list, doc|
|
43
|
+
if list.any?{|kept| not kept.different_from?(doc.parsed_body)}
|
44
|
+
list
|
45
|
+
else
|
46
|
+
list + [doc]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve_against_existing(doc, list)
|
52
|
+
old_doc = JSON.parse(doc.contents)
|
53
|
+
|
54
|
+
keep, trash = list.partition do |doc|
|
55
|
+
doc.different_from?(old_doc)
|
56
|
+
end
|
57
|
+
|
58
|
+
if trash.length == 0
|
59
|
+
@obsolete_paths << doc.path
|
60
|
+
end
|
61
|
+
|
62
|
+
return keep
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/xing/specdoc.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'xing/specdoc/module'
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
config.add_setting :specdoc_target_directory, :default => "../spec_api"
|
5
|
+
|
6
|
+
config.include(Xing::SpecDoc, :doc_path => proc{|value| !!value})
|
7
|
+
|
8
|
+
config.after(:suite) do
|
9
|
+
Xing::SpecDoc.record_docs
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'xing/specdoc/document'
|
2
|
+
|
3
|
+
describe Xing::SpecDoc::Document do
|
4
|
+
let :path do
|
5
|
+
"whatever_path"
|
6
|
+
end
|
7
|
+
|
8
|
+
subject :doccer do
|
9
|
+
Xing::SpecDoc::Document.new(path, response_json)
|
10
|
+
end
|
11
|
+
|
12
|
+
let :our_hash do
|
13
|
+
{
|
14
|
+
"number" => 127,
|
15
|
+
"string" => "a string",
|
16
|
+
"string_list" => [
|
17
|
+
"a", "b", "c"
|
18
|
+
],
|
19
|
+
"number_list" => [
|
20
|
+
1, 2, 3
|
21
|
+
],
|
22
|
+
"object" => {
|
23
|
+
"number" => 127,
|
24
|
+
"string" => "a string",
|
25
|
+
"string_list" => [
|
26
|
+
"a", "b", "c"
|
27
|
+
],
|
28
|
+
"number_list" => [
|
29
|
+
1, 2, 3
|
30
|
+
]
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
let :other_hash do
|
36
|
+
our_hash.dup
|
37
|
+
end
|
38
|
+
|
39
|
+
let :response_json do
|
40
|
+
our_hash.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
context "should not register difference" do
|
44
|
+
let :is_diff do
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
it "from an identical JSON" do
|
49
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "from changed number values" do
|
53
|
+
other_hash["number"] = 321
|
54
|
+
|
55
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "from changed string values" do
|
59
|
+
other_hash["string"] = "another string"
|
60
|
+
|
61
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "from changed list values" do
|
65
|
+
other_hash["string_list"] = [ "z","y","x" ]
|
66
|
+
other_hash["number_list"] = [ 4,5,6 ]
|
67
|
+
|
68
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "should register differences" do
|
73
|
+
let :is_diff do
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
it "when string changes to number" do
|
78
|
+
other_hash["string"] = 1234
|
79
|
+
|
80
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "when number changes to string" do
|
84
|
+
other_hash["number"] = "tuesday"
|
85
|
+
|
86
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "when a field is removed" do
|
90
|
+
other_hash.delete("string")
|
91
|
+
|
92
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "when a field is added" do
|
96
|
+
other_hash["new_string"] = "hello"
|
97
|
+
|
98
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "when a list shortens" do
|
102
|
+
other_hash["string_list"] = our_hash["string_list"][0..-2]
|
103
|
+
|
104
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "when a list lengthens" do
|
108
|
+
other_hash["number_list"] = our_hash["number_list"] + [5]
|
109
|
+
|
110
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "when a member of list changes from number to string" do
|
114
|
+
other_hash["number_list"] = our_hash["number_list"][0..-2] + ["string"]
|
115
|
+
|
116
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "when a member of list changes from string to number" do
|
120
|
+
other_hash["string_list"] = our_hash["string_list"][0..-2] + [37]
|
121
|
+
|
122
|
+
expect(doccer.different_from?(other_hash)).to eq(is_diff)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'xing/specdoc/document'
|
2
|
+
require 'xing/specdoc/winnower'
|
3
|
+
|
4
|
+
describe Xing::SpecDoc::Winnower do
|
5
|
+
subject :winnower do
|
6
|
+
Xing::SpecDoc::Winnower.new(new_docs, old_docs)
|
7
|
+
end
|
8
|
+
|
9
|
+
let :first_content do
|
10
|
+
{ "a" => "a" }.to_json
|
11
|
+
end
|
12
|
+
|
13
|
+
let :second_content do
|
14
|
+
{ "something" => "else" }.to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
let :third_content do
|
18
|
+
{ "something_completely" => "else" }.to_json
|
19
|
+
end
|
20
|
+
|
21
|
+
let :new_docs do
|
22
|
+
new_contents.each_with_index.map do |content, index|
|
23
|
+
Xing::SpecDoc::Document.new("new_path_#{index}", content)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
let :old_docs do
|
28
|
+
old_contents.each_with_index.map do |content, index|
|
29
|
+
Xing::SpecDoc::Document.new("old_path_#{index}", content)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
let :old_paths do
|
34
|
+
old_docs.map(&:path)
|
35
|
+
end
|
36
|
+
|
37
|
+
let :new_contents do
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
|
41
|
+
let :old_contents do
|
42
|
+
[]
|
43
|
+
end
|
44
|
+
|
45
|
+
context "should reduce extraneous new docs" do
|
46
|
+
let :new_contents do
|
47
|
+
[ first_content, first_content, first_content ]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should strip the extras" do
|
51
|
+
expect(winnower.kept_new.length).to eq(1)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not invent obsoletes" do
|
55
|
+
expect(winnower.obsolete_paths.length).to eq(0)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "should prefer existing docs" do
|
60
|
+
let :new_contents do
|
61
|
+
[ first_content ]
|
62
|
+
end
|
63
|
+
|
64
|
+
let :old_contents do
|
65
|
+
[ first_content ]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should strip the duplicate new docs" do
|
69
|
+
expect(winnower.kept_new.length).to eq(0)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should not consider incumbents obsolete" do
|
73
|
+
expect(winnower.obsolete_paths.length).to eq(0)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "should consider old docs without support obsolete" do
|
78
|
+
let :new_contents do
|
79
|
+
[ first_content ]
|
80
|
+
end
|
81
|
+
|
82
|
+
let :old_contents do
|
83
|
+
[ second_content ]
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should strip the duplicate new docs" do
|
87
|
+
expect(winnower.kept_new.length).to eq(1)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should not consider incumbents obsolete" do
|
91
|
+
expect(winnower.obsolete_paths.length).to eq(1)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "whole set up" do
|
96
|
+
let :new_contents do
|
97
|
+
[ first_content, second_content ]
|
98
|
+
end
|
99
|
+
|
100
|
+
let :old_contents do
|
101
|
+
[ second_content, third_content ]
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should strip the duplicate new docs" do
|
105
|
+
expect(winnower.kept_new.length).to eq(1)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should not consider incumbents obsolete" do
|
109
|
+
expect(winnower.obsolete_paths.length).to eq(1)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
#require 'ftools'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module FileSandbox
|
5
|
+
def self.included(spec)
|
6
|
+
return unless spec.respond_to? :before
|
7
|
+
|
8
|
+
spec.before do
|
9
|
+
setup_sandbox
|
10
|
+
end
|
11
|
+
|
12
|
+
spec.after do
|
13
|
+
teardown_sandbox
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class HaveContents
|
18
|
+
def initialize(contents)
|
19
|
+
@contents = contents
|
20
|
+
end
|
21
|
+
|
22
|
+
def matches?(target)
|
23
|
+
case @contents
|
24
|
+
when Regexp
|
25
|
+
@contents =~ target.contents
|
26
|
+
when String
|
27
|
+
@contents == target.contents
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def have_contents(expected)
|
33
|
+
HaveContents.new(expected)
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :sandbox
|
37
|
+
|
38
|
+
def in_sandbox(&block)
|
39
|
+
raise "I expected to create a sandbox as you passed in a block to me" if !block_given?
|
40
|
+
|
41
|
+
setup_sandbox
|
42
|
+
original_error = nil
|
43
|
+
|
44
|
+
begin
|
45
|
+
yield @sandbox
|
46
|
+
rescue => e
|
47
|
+
original_error = e
|
48
|
+
raise
|
49
|
+
ensure
|
50
|
+
begin
|
51
|
+
teardown_sandbox
|
52
|
+
rescue
|
53
|
+
if original_error
|
54
|
+
STDERR.puts "ALERT: a test raised an error and failed to release some lock(s) in the sandbox directory"
|
55
|
+
raise(original_error)
|
56
|
+
else
|
57
|
+
raise
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup_sandbox(path = '__sandbox')
|
64
|
+
unless @sandbox
|
65
|
+
@sandbox = Sandbox.new(path)
|
66
|
+
@__old_path_for_sandbox = Dir.pwd
|
67
|
+
Dir.chdir(@sandbox.root)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def teardown_sandbox
|
72
|
+
if @sandbox
|
73
|
+
Dir.chdir(@__old_path_for_sandbox)
|
74
|
+
@sandbox.clean_up
|
75
|
+
@sandbox = nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Sandbox
|
80
|
+
attr_reader :root
|
81
|
+
|
82
|
+
def initialize(path = '__sandbox')
|
83
|
+
@root = File.expand_path(path)
|
84
|
+
clean_up
|
85
|
+
FileUtils.mkdir_p @root
|
86
|
+
end
|
87
|
+
|
88
|
+
def [](name)
|
89
|
+
SandboxFile.new(File.join(@root, name), name)
|
90
|
+
end
|
91
|
+
|
92
|
+
# usage new :file=>'my file.rb', :with_contents=>'some stuff'
|
93
|
+
def new(options)
|
94
|
+
if options.has_key? :directory
|
95
|
+
dir = self[options.delete(:directory)]
|
96
|
+
FileUtils.mkdir_p dir.path
|
97
|
+
else
|
98
|
+
file = self[options.delete(:file)]
|
99
|
+
if (binary_content = options.delete(:with_binary_content) || options.delete(:with_binary_contents))
|
100
|
+
file.binary_content = binary_content
|
101
|
+
else
|
102
|
+
file.content = (options.delete(:with_content) || options.delete(:with_contents) || '')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
raise "unexpected keys '#{options.keys.join(', ')}'" unless options.empty?
|
107
|
+
|
108
|
+
dir || file
|
109
|
+
end
|
110
|
+
|
111
|
+
def remove(options)
|
112
|
+
name = File.join(@root, options[:file])
|
113
|
+
FileUtils.remove_file name
|
114
|
+
end
|
115
|
+
|
116
|
+
def clean_up
|
117
|
+
FileUtils.rm_rf @root
|
118
|
+
if File.exists? @root
|
119
|
+
raise "Could not remove directory #{@root.inspect}, something is probably still holding a lock on it"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
class SandboxFile
|
126
|
+
attr_reader :path
|
127
|
+
|
128
|
+
def initialize(path, sandbox_path)
|
129
|
+
@path = path
|
130
|
+
@sandbox_path = sandbox_path
|
131
|
+
end
|
132
|
+
|
133
|
+
def inspect
|
134
|
+
"SandboxFile: #@sandbox_path"
|
135
|
+
end
|
136
|
+
|
137
|
+
def exist?
|
138
|
+
File.exist? path
|
139
|
+
end
|
140
|
+
|
141
|
+
def content
|
142
|
+
File.read path
|
143
|
+
end
|
144
|
+
|
145
|
+
def content=(content)
|
146
|
+
FileUtils.mkdir_p File.dirname(@path)
|
147
|
+
File.open(@path, "w") {|f| f << content}
|
148
|
+
end
|
149
|
+
|
150
|
+
def binary_content=(content)
|
151
|
+
FileUtils.mkdir_p File.dirname(@path)
|
152
|
+
File.open(@path, "wb") {|f| f << content}
|
153
|
+
end
|
154
|
+
|
155
|
+
def create
|
156
|
+
self.content = ''
|
157
|
+
end
|
158
|
+
|
159
|
+
alias exists? exist?
|
160
|
+
alias contents content
|
161
|
+
alias contents= content=
|
162
|
+
alias binary_contents= binary_content=
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
puts Dir::pwd
|
2
|
+
require 'test/unit'
|
3
|
+
begin
|
4
|
+
require 'spec'
|
5
|
+
rescue LoadError
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
class RSpecTest < Test::Unit::TestCase
|
10
|
+
def test_that_rspec_is_available
|
11
|
+
assert_nothing_raised("\n\n * RSpec isn't available - please run: gem install rspec *\n\n"){ ::Spec }
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_that_specs_pass
|
15
|
+
assert(system(*%w{spec -f e -p **/*.rb spec}),"\n\n * Specs failed *\n\n")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'rspec/core/formatters/base_formatter'
|
3
|
+
require 'file-sandbox'
|
4
|
+
require 'cadre/rspec3'
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
config.add_formatter(Cadre::RSpec3::NotifyOnCompleteFormatter)
|
9
|
+
config.add_formatter(Cadre::RSpec3::QuickfixFormatter)
|
10
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xing-backend-specdoc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Judson Lester
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hashdiff
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.2'
|
27
|
+
description: ''
|
28
|
+
email:
|
29
|
+
- nyarly@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/xing/specdoc.rb
|
35
|
+
- lib/xing/specdoc/api-doccer.rb
|
36
|
+
- lib/xing/specdoc/doc-family.rb
|
37
|
+
- lib/xing/specdoc/document.rb
|
38
|
+
- lib/xing/specdoc/module.rb
|
39
|
+
- lib/xing/specdoc/patterner.rb
|
40
|
+
- lib/xing/specdoc/winnower.rb
|
41
|
+
- spec/document_spec.rb
|
42
|
+
- spec/winnower_spec.rb
|
43
|
+
- spec_help/file-sandbox.rb
|
44
|
+
- spec_help/gem_test_suite.rb
|
45
|
+
- spec_help/spec_helper.rb
|
46
|
+
homepage: http://nyarly.github.com/xing-backend-specdoc
|
47
|
+
licenses:
|
48
|
+
- MIT
|
49
|
+
metadata: {}
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- "--inline-source"
|
53
|
+
- "--main"
|
54
|
+
- doc/README
|
55
|
+
- "--title"
|
56
|
+
- xing-backend-specdoc-0.0.1 Documentation
|
57
|
+
require_paths:
|
58
|
+
- lib/
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project: xing-backend-specdoc
|
71
|
+
rubygems_version: 2.4.8
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: ''
|
75
|
+
test_files:
|
76
|
+
- spec_help/gem_test_suite.rb
|