xing-backend-specdoc 0.0.1
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 +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
|