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 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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fitquery.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
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
+
@@ -0,0 +1,3 @@
1
+ module Fitquery
2
+ VERSION = '0.1.0'
3
+ end
data/lib/fitquery.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "fitquery/version"
2
+ require "fitquery/fitnesse_root"
3
+
4
+ module Fitquery
5
+ # Your code goes here...
6
+ end
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: []