fitquery 0.1.0
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 +15 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +36 -0
- data/Rakefile +2 -0
- data/fitquery.gemspec +22 -0
- data/lib/fitquery/fitnesse_node.rb +184 -0
- data/lib/fitquery/fitnesse_root.rb +53 -0
- data/lib/fitquery/version.rb +3 -0
- data/lib/fitquery.rb +6 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NmMzOTJiM2I5ODYzMmExMzM5NTJlZjBiNGI5MDI0NWUzZDlmOTBkNw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OWI2MTAyOGI0MTkxYjM1MzgzMzdmOGY4NDY2OWIzNmMyZmNiNWRiMA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MDY2MTU3ODFjM2Y4NjQyYjkzM2Y0YTI4NGRjODdjYTJhNjUwZjE1M2QzNGM2
|
10
|
+
YTVkNzc1NGM5Yjk3YmMwMjk2Y2MxNmFmNzhhOWQzZGFkNmUwMjU1ODAwOGU0
|
11
|
+
NmM0M2ZlYTk0YWZiODFkYWMzM2Q4ZWU5MDY3MjYyODEwZjAxNmE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MDljNjM0ZjkzZmEzYjNmYmI4ZTc2ODg3OGRlNWI5MWUzYzI2NDgzMWViMjRj
|
14
|
+
ODAxZGI0MzM3MzY5OTMwMzcyZmNkYWI3OWY1NThjYzZmZTE2NjhhMTNjNWVh
|
15
|
+
NDZkNjU1ZDM4MmI0ZmVmMDg4OGMwMjRmMmI1M2Q2MGYzYzY4MmQ=
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Tony Peguero
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Fitquery
|
2
|
+
|
3
|
+
Tools for inspecting and querying a FitNesse test hierarchy.
|
4
|
+
This Ruby gem allows you to create an enumerable structure that represents an entire test hierarchy, and then inspect that
|
5
|
+
structure. For example, let's say that your organization makes heavy use of tags in FitNesse, and you would like to find
|
6
|
+
all the tests that are not included by a particular tag. That could be done like this:
|
7
|
+
|
8
|
+
fitnesse = FitnesseRoot.new("C:/gitrepos/centralrepobare/FitTest/FitNesseRoot")
|
9
|
+
untagged = fitnesse.find_all {|node|
|
10
|
+
node.runable? && node.test? && !node.has_tag?('Nightly')
|
11
|
+
}
|
12
|
+
puts untagged
|
13
|
+
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'fitquery'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install fitquery
|
28
|
+
|
29
|
+
|
30
|
+
## Contributing
|
31
|
+
|
32
|
+
1. Fork it ( https://github.com/[my-github-username]/fitquery/fork )
|
33
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
34
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
35
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
36
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/fitquery.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fitquery/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "fitquery"
|
8
|
+
spec.version = Fitquery::VERSION
|
9
|
+
spec.authors = ["Tony Peguero"]
|
10
|
+
spec.email = ["tony.peguero@payglobal.com"]
|
11
|
+
spec.summary = %q{Tools for inspecting and querying a FitNesse test hierarchy}
|
12
|
+
spec.homepage = ""
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'pathname'
|
3
|
+
require 'set'
|
4
|
+
require 'find'
|
5
|
+
|
6
|
+
# The FitnesseNode class represents a node in the Fitnesse tree, i.e. a test, suite, or static page.
|
7
|
+
# There are methods for querying tags, state, etc., which are based on the properties file.
|
8
|
+
# The content of the page is currently ignored.
|
9
|
+
|
10
|
+
# @attr_reader [FitnesseRoot] root The root of the Fitnesse tree of which this node is a part.
|
11
|
+
# @attr_reader [FitnesseNode,FitnesseRoot] parent The parent of this node (which may be the root object)
|
12
|
+
# @attr_reader [Array<FitnesseNode>] children The child nodes of this node.
|
13
|
+
# @attr_reader [Pathname] path The path of this node's folder.
|
14
|
+
# @attr_reader [String] name The name of this node. This is the "short" name of the node itself, not the fully qualified name.
|
15
|
+
# @attr_reader [Rexml::Document] properties The properties XML document of this node.
|
16
|
+
# @attr_reader [SortedSet<String>] explicit_tags The set of tags that have been explicitly set on this node.
|
17
|
+
class FitnesseNode
|
18
|
+
attr_reader :root, :parent, :children, :path, :name, :properties, :explicit_tags
|
19
|
+
|
20
|
+
|
21
|
+
def initialize(root, parent, name)
|
22
|
+
@root = root
|
23
|
+
@parent = parent
|
24
|
+
@children = []
|
25
|
+
@name = name.to_s
|
26
|
+
@path = name == :root ? parent.path : Pathname.new(parent.path.join(name))
|
27
|
+
begin
|
28
|
+
File.open(@path.join('properties.xml'), 'r') {|f| @properties = REXML::Document.new f }
|
29
|
+
rescue
|
30
|
+
@properties = nil
|
31
|
+
end
|
32
|
+
begin
|
33
|
+
tags_element = @properties.get_elements('/properties/Suites')
|
34
|
+
unless tags_element.nil?
|
35
|
+
@explicit_tags = SortedSet.new(tags_element.first.text.split(',').map{|tag| tag.strip })
|
36
|
+
end
|
37
|
+
rescue
|
38
|
+
@explicit_tags = SortedSet.new([])
|
39
|
+
end
|
40
|
+
@path.children.each do |sub|
|
41
|
+
next unless sub.directory?
|
42
|
+
rel_path = sub.relative_path_from(@root.path)
|
43
|
+
next if @root.blacklist.any? {|blacklisted| rel_path.fnmatch?(blacklisted) }
|
44
|
+
child_node = FitnesseNode.new(@root, self, sub.basename)
|
45
|
+
@children.push(child_node) unless child_node.nil?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Yield this node and its children.
|
50
|
+
# @param order [Symbol] Use :pre to yield this node before its children, or :post to yield the children first.
|
51
|
+
def traverse(order = :pre, &block)
|
52
|
+
yield self if order == :pre
|
53
|
+
unless @children.nil?
|
54
|
+
@children.each do |child|
|
55
|
+
child.traverse(&block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
yield self if order == :post
|
59
|
+
end
|
60
|
+
|
61
|
+
# Yield this node and its children.
|
62
|
+
def each(&block)
|
63
|
+
traverse(:pre, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Yield this node and its children, but provides a way to stop further recursion down this node's branch of the tree.
|
67
|
+
# @example Ignore all nodes that are the children of a node which is marked as skipped.
|
68
|
+
# start_node.find {|node|
|
69
|
+
# Find.prune if node.explicitly_skipped?
|
70
|
+
# # only gets to here if none of the current node's ancestors have been skipped.
|
71
|
+
# }
|
72
|
+
def find(&block)
|
73
|
+
catch(:prune) do
|
74
|
+
yield self
|
75
|
+
unless @children.nil?
|
76
|
+
@children.each do |child|
|
77
|
+
child.find(&block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Determines the set of tags that are set on this node or any of its ancestors.
|
84
|
+
# @return [SortedSet<String>] The set of tags that apply to this node.
|
85
|
+
def effective_tags
|
86
|
+
if @parent.respond_to?(:effective_tags)
|
87
|
+
@parent.effective_tags + @explicit_tags
|
88
|
+
else
|
89
|
+
@explicit_tags
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Boolean] Does this node have the Prune property explicitly set on it?
|
94
|
+
def explicitly_skipped?
|
95
|
+
@properties.nil? ? false : (@properties.get_elements('/properties/Prune').count > 0)
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [Boolean] Does this node have the Prune property set on it or on any of its ancestors?
|
99
|
+
def effectively_skipped?
|
100
|
+
if @parent.respond_to?(:effectively_skipped?)
|
101
|
+
@parent.effectively_skipped? || explicitly_skipped?
|
102
|
+
else
|
103
|
+
explicitly_skipped?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Gets the fully qualified name of this node.
|
108
|
+
# @param sep [String] Sets the preferred separator string for the name. If nil, the name will use the system default file path separator.
|
109
|
+
# @return [String] The fully qualified name of the node, consisting of the names of each node in the tree leading to this one.
|
110
|
+
def full_name(sep = nil)
|
111
|
+
rel_path = @path.relative_path_from(@root.path)
|
112
|
+
if sep.nil?
|
113
|
+
rel_path.to_s
|
114
|
+
else
|
115
|
+
rel_path.to_s.gsub(Pathname::SEPARATOR_PAT, sep)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Gets the depth of this node, i.e. the number of levels down the tree from the root node.
|
120
|
+
# @return [Integer] The depth of the node
|
121
|
+
def depth
|
122
|
+
rel_path = @path.relative_path_from(@root.path)
|
123
|
+
rel_path.to_s.scan(Pathname::SEPARATOR_PAT).size
|
124
|
+
end
|
125
|
+
|
126
|
+
# Gets the name of the node, prefixed with a number of characters determined by the node's depth.
|
127
|
+
# Handy for displaying the node in a simple tree representation.
|
128
|
+
# @param indent_string [String] The string from which to construct the indentation.
|
129
|
+
# @return [String] The indented name.
|
130
|
+
# @example A node with a full name of 'Root/Foo/Bar/Baz'
|
131
|
+
# node.indented_name('-') => '---Baz'
|
132
|
+
def indented_name(indent_string = ' ')
|
133
|
+
indent = indent_string * depth
|
134
|
+
indent + @name.to_s
|
135
|
+
end
|
136
|
+
|
137
|
+
# @return [Boolean] Is this node defined as a Test?
|
138
|
+
def test?
|
139
|
+
@properties.nil? ? false : (@properties.get_elements('/properties/Test').count > 0)
|
140
|
+
end
|
141
|
+
|
142
|
+
# @return [Boolean] Is this node defined as a Suite?
|
143
|
+
def suite?
|
144
|
+
@properties.nil? ? false : (@properties.get_elements('/properties/Suite').count > 0)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [Boolean] Is this node defined as a Static page?
|
148
|
+
def static?
|
149
|
+
@properties.nil? ? false : (@properties.get_elements('/properties/Static').count > 0)
|
150
|
+
end
|
151
|
+
|
152
|
+
# @return [Boolean] Is this node runable? I.e. is it either a test or a suite which is not skipped?
|
153
|
+
def runable?
|
154
|
+
!static? && !effectively_skipped? && (test? || suite?)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Determine whether this node has a particular tag on it.
|
158
|
+
# @param tag [String,Regexp] The tag to find. If a string, the search will be case insensitive.
|
159
|
+
# If a regular expression is used, the regex's case sensitivity flag will be respected.
|
160
|
+
# @param explicit_only [Boolean] Specify whether to search within the explicit tags or the effective tags of this node.
|
161
|
+
def has_tag?(tag, explicit_only = false)
|
162
|
+
tag_set = explicit_only ? explicit_tags : effective_tags
|
163
|
+
if tag.instance_of?(Regexp)
|
164
|
+
pattern = tag
|
165
|
+
elsif tag.instance_of?(String)
|
166
|
+
pattern = /^#{tag}$/i
|
167
|
+
end
|
168
|
+
tag_set.any? {|t| t.match(pattern) }
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_s
|
172
|
+
full_name
|
173
|
+
end
|
174
|
+
|
175
|
+
def <=>(other)
|
176
|
+
if depth == other.depth
|
177
|
+
name <=> other.name
|
178
|
+
else
|
179
|
+
depth <=> other.depth
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require_relative 'fitnesse_node'
|
3
|
+
|
4
|
+
# The FitnesseRoot class represents an entire tree of Fitnesse tests/suites.
|
5
|
+
# @attr_reader [Pathname] path The root path of the Fitnesse tree.
|
6
|
+
# @attr_reader [Array<String>] blacklist An array of names that should be ignored during the initial tree inspection.
|
7
|
+
class FitnesseRoot
|
8
|
+
include Enumerable
|
9
|
+
attr_reader :path, :blacklist
|
10
|
+
|
11
|
+
STANDARD_BLACKLIST = ["files", "FitNesse", "FrontPage", "HelpMenu", "ErrorLogs", "Recent Changes"]
|
12
|
+
|
13
|
+
def initialize(path, blacklist = STANDARD_BLACKLIST)
|
14
|
+
@path = Pathname.new(path)
|
15
|
+
@blacklist = blacklist
|
16
|
+
@root_node = FitnesseNode.new(self, self, :root)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Print a summary of the entire tree, indicating types, skipped status, and effective tags on each node.
|
20
|
+
def print_summary
|
21
|
+
traverse do |node|
|
22
|
+
print node.effectively_skipped? ? '-' : '+'
|
23
|
+
case
|
24
|
+
when node.test? then print 'T'
|
25
|
+
when node.suite? then print 'S'
|
26
|
+
when node.static? then print 'X'
|
27
|
+
else print '?'
|
28
|
+
end
|
29
|
+
print node.indented_name(' ')
|
30
|
+
tags = node.effective_tags.to_a
|
31
|
+
unless tags.empty?
|
32
|
+
# highlight the tags that are explicit on this node
|
33
|
+
tags = tags.map {|tag| node.explicit_tags.include?(tag) ? "*#{tag}" : tag }
|
34
|
+
print " [#{tags.join(',')}]"
|
35
|
+
end
|
36
|
+
print "\n"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def traverse(order = :pre, &block)
|
41
|
+
@root_node.traverse(order, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def each(&block)
|
45
|
+
@root_node.traverse(:pre, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def find(&block)
|
49
|
+
@root_node.find(&block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
data/lib/fitquery.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fitquery
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tony Peguero
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- tony.peguero@payglobal.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- fitquery.gemspec
|
54
|
+
- lib/fitquery.rb
|
55
|
+
- lib/fitquery/fitnesse_node.rb
|
56
|
+
- lib/fitquery/fitnesse_root.rb
|
57
|
+
- lib/fitquery/version.rb
|
58
|
+
homepage: ''
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.2.2
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: Tools for inspecting and querying a FitNesse test hierarchy
|
82
|
+
test_files: []
|