earl-report 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,42 +1,13 @@
1
1
  # earl-report
2
- ============
3
-
4
2
  Ruby gem to consolidate multiple EARL report and generate a rollup conformance report.
5
3
 
6
4
  ## Description
7
-
8
5
  Reads a test manifest in the
9
6
  [standard RDF WG format](http://www.w3.org/2011/rdf-wg/wiki/Turtle_Test_Suite)
10
- and generates a rollup report in RDFa+HTML.
11
-
12
- ## Test Specifications
13
- The test manifest is presumed to be of the following form:
14
-
15
- ### Manifest Header
16
-
17
- The manifest header looks like:
18
-
19
- <> rdf:type mf:Manifest ;
20
- rdfs:comment "Turtle tests" ;
21
- mf:entries
22
- (
23
- ....
24
- ) .
25
-
26
- where .... is a list of links to test descriptions, one per line.
27
-
28
- ### Test description
29
-
30
- This is an example of a syntax test:
31
-
32
- <#turtle-syntax-file-01> rdf:type rdft:TestTurtlePositiveSyntax ;
33
- mf:name "turtle-syntax-file-01" ;
34
- rdfs:comment "Further description of the test" ;
35
- mf:action <turtle-syntax-file-01.ttl> ;
36
- mf:result <turtle-eval-struct-01.nt> .
7
+ along with one or more individual EARL reports and generates a rollup report in
8
+ HTML+RDFa in [ReSpec][] format.
37
9
 
38
10
  ## Individual EARL reports
39
-
40
11
  Results for individual implementations should be specified in Turtle form, but
41
12
  may be specified in an any compatible RDF serialization (JSON-LD is presumed to
42
13
  be a cached rollup report). The report is composed of `Assertion` declarations
@@ -75,9 +46,35 @@ of the following form:
75
46
  If not found, the IRI identified by `doap:developer`
76
47
  will be dereferenced and is presumed to provide a [FOAF]() profile of the developer.
77
48
 
78
- ## Usage
49
+ ## Manifest query
50
+ The test manifest is used to generate `earl:TestCase` entries for each test
51
+ described in the test manifest. It will also summarize each test, including
52
+ any input and result files associated with the tests. The built-in query
53
+ is based on the [standard RDF WG format](). Alternative manifest formats
54
+ can be used by specifying a customized manifest query. The default query
55
+ is the following:
56
+
57
+ PREFIX dc: <http://purl.org/dc/terms/>
58
+ PREFIX mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#>
59
+ PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
60
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
61
+
62
+ SELECT ?lh ?uri ?title ?description ?testAction ?testResult
63
+ WHERE {
64
+ ?uri mf:name ?title; mf:action ?testAction.
65
+ OPTIONAL { ?uri rdfs:comment ?description. }
66
+ OPTIONAL { ?uri mf:result ?testResult. }
67
+ OPTIONAL { [ mf:entries ?lh] . ?lh rdf:first ?uri . }
68
+ }
69
+
70
+ If any result has a non-null `?lh`, it is taken as the list head and used
71
+ to maintain the list order within `earl:tests`.
72
+
73
+ ## Report generation template
74
+ The report template is in [ReSpec][] form using [Haml]() to generate individual report elements.
79
75
 
80
- The `earl` command may be used to directly create a report from zero or more input files, which are themselves [EARL][] report.
76
+ ## Usage
77
+ The `earl-report` command may be used to directly create a report from zero or more input files, which are themselves [EARL][] report.
81
78
 
82
79
  gem install earl-report
83
80
 
@@ -86,12 +83,15 @@ The `earl` command may be used to directly create a report from zero or more inp
86
83
  --tempate [FILE] # Location of report template file; returns default if not specified
87
84
  --bibRef # The default ReSpec-formatted bibliographic reference for the report
88
85
  --name # The name of the software being reported upon
89
- manifest # one or more test manifests used to define test descriptions
86
+ --manifest FILE # a test manifest used to define test descriptions
87
+ --base URI # Base URI to by applied when parsing test manifest
88
+ --query FILE # Alternative SPARQL query for extracting information from manifest
90
89
  report* # one or more EARL report in most RDF formats
91
90
 
92
- ## Report generation template
93
-
94
- The report template is in ReSpec form using [Haml]() to generate individual elements.
91
+ ### Initialization File
92
+ `earl-report` can take defaults for options from an initialization file.
93
+ When run, `earl-report` attempts to open the file `.earl` in the current directory.
94
+ This file is in [YAML][] format with entries for each option.
95
95
 
96
96
  ## License
97
97
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.1.0
data/bin/earl-report CHANGED
@@ -4,16 +4,21 @@ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", 'lib')))
4
4
 
5
5
  require 'earl_report'
6
6
  require 'getoptlong'
7
+ require 'yaml'
7
8
 
8
9
  def run(input, options)
9
10
  end
10
11
 
11
12
  OPT_ARGS = [
13
+ ["--base", GetoptLong::REQUIRED_ARGUMENT,"Base URI to use when loading test manifest"],
12
14
  ["--bibRef", GetoptLong::REQUIRED_ARGUMENT,"ReSpec BibRef of specification being reported upon"],
13
15
  ["--format", "-f", GetoptLong::REQUIRED_ARGUMENT,"Format of output, one of 'ttl', 'json', or 'html'"],
14
16
  ["--json", GetoptLong::NO_ARGUMENT, "Input is a JSON-LD formatted result"],
17
+ ["--manifest", GetoptLong::REQUIRED_ARGUMENT,"Test manifest"],
15
18
  ["--name", GetoptLong::REQUIRED_ARGUMENT,"Name of specification"],
16
19
  ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT,"Output report to file"],
20
+ ["--query", GetoptLong::REQUIRED_ARGUMENT,"Query, or file containing query for extracting information from Test manifest"],
21
+ ["--rc", GetoptLong::NO_ARGUMENT, "Write options to run-control file"],
17
22
  ["--template", GetoptLong::OPTIONAL_ARGUMENT,"Specify or return default report template"],
18
23
  ["--verbose", GetoptLong::NO_ARGUMENT, "Detail on execution"],
19
24
  ["--help", "-?", GetoptLong::NO_ARGUMENT, "This message"]
@@ -22,6 +27,9 @@ def usage
22
27
  STDERR.puts %{
23
28
  Generate EARL report for mutliple test results against a test manifest.
24
29
 
30
+ Options are initialized by reading optional run-control file '.earl' in the local directory,
31
+ if it exists.
32
+
25
33
  Usage: #{$0} [options] test-manifest test-result ...
26
34
  }.gsub(/^ /, '')
27
35
  width = OPT_ARGS.map do |o|
@@ -42,13 +50,20 @@ opts = GetoptLong.new(*OPT_ARGS.map {|o| o[0..-2]})
42
50
  options = {
43
51
  :format => :html,
44
52
  :io => STDOUT}
53
+ options.merge!(YAML.load(File.open ".earl")) if File.exist?(".earl")
45
54
 
46
55
  opts.each do |opt, arg|
47
56
  case opt
57
+ when '--base' then options[:base] = arg
48
58
  when '--bibRef' then options[:bibRef] = arg
49
59
  when '--format' then options[:format] = arg.to_sym
60
+ when '--manifest' then options[:manifest] = arg
61
+ when '--query' then options[:manifest] = arg
62
+ when '--base' then options[:base] = arg
63
+ when '--json' then options[:json] = true
50
64
  when '--name' then options[:name] = arg
51
65
  when '--output' then options[:io] = File.open(arg, "w")
66
+ when '--rc' then options[:rc] = true
52
67
  when '--template' then options[:template] = arg
53
68
  when '--verbose' then options[:verbose] = true
54
69
  when '--help' then usage
@@ -57,6 +72,19 @@ opts.each do |opt, arg|
57
72
  end
58
73
  end
59
74
 
75
+ # Replace query from a specified file, with the query itself
76
+ if options.has_key?(:query) && File.exist?(options[:query])
77
+ options[:query] = File.read(options[:query])
78
+ end
79
+
80
+ # Write run-control file to output
81
+ if options.has_key?(:rc)
82
+ io = options.delete(:io) || STDOUT
83
+ options.delete_if {|k, v| [:rc, :json, :output, :verbose].include?(k)}
84
+ io.puts options.to_yaml
85
+ exit 0
86
+ end
87
+
60
88
  # If requesting template, just return it
61
89
  if options.has_key?(:template) && options[:template].empty?
62
90
  File.open(File.expand_path("../../lib/earl_report/views/earl_report.html.haml", __FILE__)) do |f|
@@ -64,5 +92,5 @@ if options.has_key?(:template) && options[:template].empty?
64
92
  end
65
93
  else
66
94
  earl = EarlReport.new(*ARGV, options)
67
- earl.generate(options.merge(:source_files => ARGV))
95
+ earl.generate(options)
68
96
  end
data/lib/earl_report.rb CHANGED
@@ -8,19 +8,36 @@ require 'haml'
8
8
  # Instantiate a new class using one or more input graphs
9
9
  class EarlReport
10
10
  attr_reader :graph
11
+
12
+ MANIFEST_QUERY = %(
13
+ PREFIX dc: <http://purl.org/dc/terms/>
14
+ PREFIX mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#>
15
+ PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
16
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
17
+
18
+ SELECT ?lh ?uri ?title ?description ?testAction ?testResult
19
+ WHERE {
20
+ ?uri mf:name ?title; mf:action ?testAction.
21
+ OPTIONAL { ?uri rdfs:comment ?description. }
22
+ OPTIONAL { ?uri mf:result ?testResult. }
23
+ OPTIONAL { [ mf:entries ?lh] . ?lh rdf:first ?uri . }
24
+ }
25
+ ).freeze
26
+
11
27
  TEST_SUBJECT_QUERY = %(
12
28
  PREFIX doap: <http://usefulinc.com/ns/doap#>
13
29
  PREFIX foaf: <http://xmlns.com/foaf/0.1/>
14
30
 
15
- SELECT DISTINCT ?uri ?name ?developer ?dev_name ?dev_type ?doap_desc ?homepage ?language
31
+ SELECT DISTINCT ?uri ?name ?doapDesc ?homepage ?language ?developer ?devName ?devType ?devHomepage
16
32
  WHERE {
17
33
  ?uri a doap:Project; doap:name ?name .
18
34
  OPTIONAL { ?uri doap:developer ?developer .}
19
35
  OPTIONAL { ?uri doap:homepage ?homepage . }
20
- OPTIONAL { ?uri doap:description ?doap_desc . }
36
+ OPTIONAL { ?uri doap:description ?doapDesc . }
21
37
  OPTIONAL { ?uri doap:programming-language ?language . }
22
- OPTIONAL { ?developer foaf:name ?dev_name .}
23
- OPTIONAL { ?developer a ?dev_type . }
38
+ OPTIONAL { ?developer foaf:name ?devName .}
39
+ OPTIONAL { ?developer a ?devType . }
40
+ OPTIONAL { ?developer foaf:homepage ?devHomepage . }
24
41
  }
25
42
  ).freeze
26
43
 
@@ -35,7 +52,7 @@ class EarlReport
35
52
  ?subject a doap:Project; doap:name ?name
36
53
  }
37
54
  }
38
- )
55
+ ).freeze
39
56
 
40
57
  ASSERTION_QUERY = %(
41
58
  PREFIX earl: <http://www.w3.org/ns/earl#>
@@ -49,8 +66,39 @@ class EarlReport
49
66
  earl:subject ?subject;
50
67
  earl:test ?test ] .
51
68
  }
69
+ ORDER BY ?subject
52
70
  ).freeze
53
71
 
72
+ TEST_CONTEXT = {
73
+ "@vocab" => "http://www.w3.org/ns/earl#",
74
+ "foaf:homepage" => {"@type" => "@id"},
75
+ dc: "http://purl.org/dc/terms/",
76
+ doap: "http://usefulinc.com/ns/doap#",
77
+ earl: "http://www.w3.org/ns/earl#",
78
+ mf: "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#",
79
+ foaf: "http://xmlns.com/foaf/0.1/",
80
+ rdfs: "http://www.w3.org/2000/01/rdf-schema#",
81
+ assertedBy: {"@type" => "@id"},
82
+ assertions: {"@type" => "@id", "@container" => "@list"},
83
+ bibRef: {"@id" => "dc:bibliographicCitation"},
84
+ description: {"@id" => "dc:description"},
85
+ developer: {"@id" => "doap:developer", "@type" => "@id", "@container" => "@set"},
86
+ doapDesc: {"@id" => "doap:description"},
87
+ homepage: {"@id" => "doap:homepage", "@type" => "@id"},
88
+ label: {"@id" => "rdfs:label"},
89
+ language: {"@id" => "doap:programming-language"},
90
+ mode: {"@type" => "@id"},
91
+ name: {"@id" => "doap:name"},
92
+ outcome: {"@type" => "@id"},
93
+ subject: {"@type" => "@id"},
94
+ test: {"@type" => "@id"},
95
+ testAction: {"@id" => "mf:action", "@type" => "@id"},
96
+ testResult: {"@id" => "mf:result", "@type" => "@id"},
97
+ tests: {"@type" => "@id", "@container" => "@list"},
98
+ testSubjects: {"@type" => "@id", "@container" => "@list"},
99
+ title: {"@id" => "dc:title"}
100
+ }.freeze
101
+
54
102
  # Convenience vocabularies
55
103
  class EARL < RDF::Vocabulary("http://www.w3.org/ns/earl#"); end
56
104
  class MF < RDF::Vocabulary("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#"); end
@@ -60,18 +108,36 @@ class EarlReport
60
108
  # @param [Array<String>] *files Assertions
61
109
  # @param [Hash{Symbol => Object}] options
62
110
  # @option options [Boolean] :verbose (true)
111
+ # @option options [String] :base Base IRI for loading Manifest
112
+ # @option options [String] :bibRef
113
+ # ReSpec bibliography reference for specification being tested
114
+ # @option options [String] :json Result of previous JSON-LD generation
115
+ # @option options [String] :manifest Test manifest
116
+ # @option options [String] :name Name of specification
117
+ # @option options [String] :query
118
+ # Query, or file containing query for extracting information from Test manifest
63
119
  def initialize(*files)
64
120
  @options = files.last.is_a?(Hash) ? files.pop.dup : {}
65
- @graph = RDF::Graph.new
121
+ @options[:query] ||= MANIFEST_QUERY
122
+ raise "Test Manifest must be specified with :manifest option" unless @options[:manifest] || @options[:json]
123
+ @files = files
66
124
  @prefixes = {}
125
+ if @options[:json]
126
+ @json_hash = ::JSON.parse(File.read(files.first))
127
+ return
128
+ end
129
+
130
+ # Load manifest, possibly with base URI
131
+ status "read #{@options[:manifest]}"
132
+ man_opts = {}
133
+ man_opts[:base_uri] = RDF::URI(@options[:base]) if @options[:base]
134
+ @graph = RDF::Graph.load(@options[:manifest], man_opts)
135
+ status " loaded #{@graph.count} triples"
136
+
137
+ # Read test assertion files
67
138
  files.flatten.each do |file|
68
139
  status "read #{file}"
69
- file_graph = case file
70
- when /\.jsonld/
71
- @json_hash = ::JSON.parse(File.read(file))
72
- return
73
- else RDF::Graph.load(file)
74
- end
140
+ file_graph = RDF::Graph.load(file)
75
141
  status " loaded #{file_graph.count} triples"
76
142
  @graph << file_graph
77
143
  end
@@ -96,7 +162,7 @@ class EarlReport
96
162
  # Load developers referenced from Test Subjects
97
163
  SPARQL.execute(TEST_SUBJECT_QUERY, @graph).each do |solution|
98
164
  # Load DOAP definitions
99
- if solution[:developer] && !solution[:dev_name] # not loaded
165
+ if solution[:developer] && !solution[:devName] # not loaded
100
166
  status "read description for #{solution[:developer].inspect}"
101
167
  begin
102
168
  foaf_graph = RDF::Graph.load(solution[:developer])
@@ -116,37 +182,28 @@ class EarlReport
116
182
  #
117
183
  # @param [Hash{Symbol => Object}] options
118
184
  # @option options [Symbol] format (:html)
119
- # @option options [String] :bibRef
120
- # ReSpec bibliography reference for specification being tested
121
- # @option options [Array<String>] :source_files
122
- # Used for referencing the files used to generate this report
123
185
  # @option options[IO] :io
124
186
  # Optional `IO` to output results
125
187
  # @return [String] serialized graph, if `io` is nil
126
188
  def generate(options = {})
127
- options = {
128
- format: :html,
129
- bibRef: "[[TURTLE]]",
130
- name: "Turtle Test Results",
131
- }.merge(options)
189
+ options = {:format => :html}.merge(options)
132
190
 
133
191
  io = options[:io]
134
192
 
135
193
  status("generate: #{options[:format]}")
136
194
  ##
137
195
  # Retrieve Hashed information in JSON-LD format
138
- hash = json_hash(options)
139
196
  case options[:format]
140
197
  when :jsonld, :json
141
- json = hash.to_json(JSON::LD::JSON_STATE)
198
+ json = json_hash.to_json(JSON::LD::JSON_STATE)
142
199
  io.write(json) if io
143
200
  json
144
201
  when :turtle, :ttl
145
202
  if io
146
- earl_turtle(options.merge(:json_hash => hash))
203
+ earl_turtle(options)
147
204
  else
148
205
  io = StringIO.new
149
- earl_turtle(:json_hash => hash, :io => io)
206
+ earl_turtle(options.merge(:io => io))
150
207
  io.rewind
151
208
  io.read
152
209
  end
@@ -155,8 +212,7 @@ class EarlReport
155
212
  File.read(File.expand_path('../earl_report/views/earl_report.html.haml', __FILE__))
156
213
 
157
214
  # Generate HTML report
158
- html = Haml::Engine.new(template, :format => :xhtml)
159
- .render(self, :tests => hash, :source_files => options.fetch(:source_files, []))
215
+ html = Haml::Engine.new(template, :format => :xhtml).render(self, :tests => json_hash)
160
216
  io.write(html) if io
161
217
  html
162
218
  else
@@ -173,39 +229,16 @@ class EarlReport
173
229
  ##
174
230
  # Return hashed EARL report in JSON-LD form
175
231
  # @return [Hash]
176
- def json_hash(options)
232
+ def json_hash
177
233
  @json_hash ||= begin
178
234
  # Customized JSON-LD output
179
235
  {
180
- "@context" => {
181
- dc: "http://purl.org/dc/terms/",
182
- doap: "http://usefulinc.com/ns/doap#",
183
- earl: "http://www.w3.org/ns/earl#",
184
- mf: "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#",
185
- foaf: "http://xmlns.com/foaf/0.1/",
186
- rdfs: "http://www.w3.org/2000/01/rdf-schema#",
187
- assertedBy: {"@id" => "earl:assertedBy", "@type" => "@id"},
188
- bibRef: {"@id" => "dc: bibliographicCitation"},
189
- description: {"@id" => "dc:description"},
190
- developer: {"@id" => "doap:developer", "@type" => "@id", "@container" => "@set"},
191
- homepage: {"@id" => "doap:homepage", "@type" => "@id"},
192
- doap_desc: {"@id" => "doap:description"},
193
- language: {"@id" => "doap:programming-language"},
194
- testAction: {"@id" => "mf:action", "@type" => "@id"},
195
- testResult: {"@id" => "mf:result", "@type" => "@id"},
196
- label: {"@id" => "rdfs:label"},
197
- mode: {"@id" => "earl:mode", "@type" => "@id"},
198
- name: {"@id" => "doap:name"},
199
- outcome: {"@id" => "earl:outcome", "@type" => "@id"},
200
- result: {"@id" => "earl:result"},
201
- subject: {"@id" => "earl:subject", "@type" => "@id"},
202
- test: {"@id" => "earl:test", "@type" => "@id"},
203
- title: {"@id" => "dc:title"}
204
- },
236
+ "@context" => TEST_CONTEXT,
205
237
  "@id" => "",
206
238
  "@type" => %w(earl:Software doap:Project),
207
- 'name' => options[:name],
208
- 'bibRef' => options[:bibRef],
239
+ "assertions" => @files,
240
+ 'name' => @options[:name],
241
+ 'bibRef' => @options[:bibRef],
209
242
  'testSubjects' => json_test_subject_info,
210
243
  'tests' => json_result_info
211
244
  }
@@ -217,69 +250,87 @@ class EarlReport
217
250
  # @return [Array]
218
251
  def json_test_subject_info
219
252
  # Get the set of subjects
220
- ts_info = {}
221
- SPARQL.execute(TEST_SUBJECT_QUERY, @graph).each do |solution|
222
- status "solution #{solution.to_hash.inspect}"
223
- info = ts_info[solution[:uri].to_s] ||= {}
224
- %w(name doap_desc homepage language).each do |prop|
225
- info[prop] = solution[prop.to_sym].to_s if solution[prop.to_sym]
226
- end
227
- if solution[:dev_name]
228
- dev_type = solution[:dev_type].to_s =~ /Organization/ ? "foaf:Organization" : "foaf:Person"
229
- dev = {'@type' => dev_type}
230
- dev['@id'] = solution[:developer].to_s if solution[:developer].uri?
231
- dev['foaf:name'] = solution[:dev_name].to_s if solution[:dev_name]
232
- (info['developer'] ||= []) << dev
253
+ @subject_info ||= begin
254
+ ts_info = {}
255
+ SPARQL.execute(TEST_SUBJECT_QUERY, @graph).each do |solution|
256
+ status "solution #{solution.to_hash.inspect}"
257
+ info = ts_info[solution[:uri].to_s] ||= {}
258
+ %w(name doapDesc homepage language).each do |prop|
259
+ info[prop] = solution[prop.to_sym].to_s if solution[prop.to_sym]
260
+ end
261
+ if solution[:devName]
262
+ dev_type = solution[:devType].to_s =~ /Organization/ ? "foaf:Organization" : "foaf:Person"
263
+ dev = {'@type' => dev_type}
264
+ dev['@id'] = solution[:developer].to_s if solution[:developer].uri?
265
+ dev['foaf:name'] = solution[:devName].to_s if solution[:devName]
266
+ dev['foaf:homepage'] = solution[:devHomepage].to_s if solution[:devHomepage]
267
+ (info['developer'] ||= []) << dev
268
+ end
269
+ info['developer'] = info['developer'].uniq
233
270
  end
234
- info['developer'] = info['developer'].uniq
235
- end
236
271
 
237
- # Map ids and values to array entries
238
- ts_info.keys.map do |id|
239
- info = ts_info[id]
240
- subject = Hash.ordered
241
- subject["@id"] = id
242
- subject["@type"] = %w(earl:TestSubject doap:Project)
243
- %w(name developer doap_desc homepage language).each do |prop|
244
- subject[prop] = info[prop] if info[prop]
272
+ # Map ids and values to array entries
273
+ ts_info.keys.sort.map do |id|
274
+ info = ts_info[id]
275
+ subject = Hash.ordered
276
+ subject["@id"] = id
277
+ subject["@type"] = %w(earl:TestSubject doap:Project)
278
+ %w(name developer doapDesc homepage language).each do |prop|
279
+ subject[prop] = info[prop] if info[prop]
280
+ end
281
+ subject
245
282
  end
246
- subject
247
283
  end
248
284
  end
249
-
285
+
250
286
  ##
251
- # Return result information for each test
287
+ # Return result information for each test.
288
+ # This counts on hash maintaining insertion order
252
289
  #
253
290
  # @return [Array]
254
291
  def json_result_info
255
292
  test_cases = {}
293
+ subjects = json_test_subject_info.map {|s| s['@id']}
256
294
 
257
- @graph.query(:predicate => MF['entries']) do |stmt|
258
- # Iterate through the test manifest and write out a TestCase
259
- # for each test
260
- RDF::List.new(stmt.object, @graph).map do |tc|
261
- tc_hash = {}
262
- tc_hash['@id'] = tc.to_s
263
- tc_hash['@type'] = %w(earl:TestCriterion earl:TestCase)
264
-
265
- # Extract important properties
266
- @graph.query(:subject => tc).each do |tc_stmt|
267
- case tc_stmt.predicate.to_s
268
- when MF['name'].to_s
269
- tc_hash['title'] = tc_stmt.object.to_s
270
- when RDF::RDFS.comment.to_s
271
- tc_hash['description'] = tc_stmt.object.to_s
272
- when MF.action.to_s
273
- tc_hash['testAction'] = tc_stmt.object.to_s
274
- when MF.result.to_s
275
- tc_hash['testResult'] = tc_stmt.object.to_s
276
- else
277
- #STDERR.puts "TC soln: #{tc_stmt.inspect}"
278
- end
279
- end
295
+ # Hash test cases by URI
296
+ solutions = SPARQL.execute(@options[:query], @graph)
297
+ .to_a
298
+ .inject({}) {|memo, soln| memo[soln[:uri]] = soln; memo}
299
+
300
+ # If test cases are in a list, maintain order
301
+ solution_list = if first_soln = solutions.values.detect {|s| s[:lh]}
302
+ RDF::List.new(first_soln[:lh], @graph)
303
+ else
304
+ solutions.keys # Any order will do
305
+ end
280
306
 
281
- test_cases[tc.to_s] = tc_hash
307
+ # Collect each TestCase
308
+ solution_list.each do |uri|
309
+ solution = solutions[uri]
310
+ tc_hash = {
311
+ '@id' => uri.to_s,
312
+ '@type' => %w(earl:TestCriterion earl:TestCase),
313
+ 'title' => solution[:title].to_s,
314
+ 'testAction' => solution[:testAction].to_s,
315
+ 'assertions' => []
316
+ }
317
+ tc_hash['description'] = solution[:description].to_s if solution[:description]
318
+ tc_hash['testResult'] = solution[:testResult].to_s if solution[:testResult]
319
+
320
+ # Pre-initialize results for each subject to untested
321
+ subjects.each do |siri|
322
+ tc_hash['assertions'] << {
323
+ '@type' => 'earl:Assertion',
324
+ 'test' => uri.to_s,
325
+ 'subject' => siri,
326
+ 'result' => {
327
+ '@type' => 'earl:TestResult',
328
+ 'outcome' => 'earl:untested'
329
+ }
330
+ }
282
331
  end
332
+
333
+ test_cases[uri.to_s] = tc_hash
283
334
  end
284
335
 
285
336
  raise "No test cases found" if test_cases.empty?
@@ -289,33 +340,23 @@ class EarlReport
289
340
  SPARQL.execute(ASSERTION_QUERY, @graph).each do |solution|
290
341
  tc = test_cases[solution[:test].to_s]
291
342
  STDERR.puts "No test case found for #{solution[:test]}: #{tc.inspect}" unless tc
292
- STDERR.puts "Must have outcome earl:passed or earl:failed: #{solution[:outcome].inspect}" unless
293
- [EARL.passed, EARL.failed].include?(solution[:outcome])
294
- tc ||= {}
295
343
  subject = solution[:subject].to_s
296
- ta_hash = {}
297
- ta_hash['@type'] = 'earl:Assertion'
344
+ result_index = subjects.index(subject)
345
+ ta_hash = tc['assertions'][result_index]
298
346
  ta_hash['assertedBy'] = solution[:by].to_s
299
- ta_hash['test'] = solution[:test].to_s
300
347
  ta_hash['mode'] = "earl:#{solution[:mode].to_s.split('#').last || 'automatic'}"
301
- ta_hash['subject'] = subject
302
- ta_hash['result'] = {
303
- '@type' => 'earl:TestResult',
304
- "outcome" => (solution[:outcome] == EARL.passed ? 'earl:passed' : 'earl:failed')
305
- }
306
- tc[subject] = ta_hash
348
+ ta_hash['result']['outcome'] = "earl:#{solution[:outcome].to_s.split('#').last}"
307
349
  end
308
350
 
309
351
  test_cases.values
310
352
  end
311
-
353
+
312
354
  ##
313
355
  # Output consoloated EARL report as Turtle
314
356
  # @param [IO, StringIO] io
315
357
  # @return [String]
316
358
  def earl_turtle(options)
317
359
  io = options[:io]
318
- json_hash = options[:json_hash]
319
360
  # Write preamble
320
361
  {
321
362
  :dc => RDF::DC,
@@ -334,24 +375,22 @@ class EarlReport
334
375
  io.puts
335
376
 
336
377
  # Write earl:Software for the report
337
- io.puts %(<#{json_hash['@id']}> a earl:Software, doap:Project;)
338
- io.puts %( doap:homepage <#{json_hash['homepage']}>;)
339
- io.puts %( doap:name "#{json_hash['name']}".)
378
+ io.puts %{<#{json_hash['@id']}> a earl:Software, doap:Project;}
379
+ io.puts %{ doap:homepage <#{json_hash['homepage']}>;}
380
+ io.puts %{ doap:name "#{json_hash['name']}";}
381
+ io.puts %{ dc:bibliographicCitation "#{json_hash['bibRef']}";}
382
+ io.puts %{ earl:assertions\n}
383
+ io.puts %{ } + json_hash['assertions'].map {|a| as_resource(a)}.join(",\n ") + ';'
384
+ io.puts %{ earl:testSubjects (\n}
385
+ io.puts %{ } + json_hash['testSubjects'].map {|a| as_resource(a['@id'])}.join("\n ") + ');'
386
+ io.puts %{ earl:tests (\n}
387
+ io.puts %{ } + json_hash['tests'].map {|a| as_resource(a['@id'])}.join("\n ") + ') .'
340
388
 
341
389
  # Test Cases
342
390
  # also collect each assertion definition
343
391
  test_cases = {}
344
392
  assertions = []
345
393
 
346
- # Tests
347
- json_hash['tests'].each do |test_case|
348
- tc_desc = test_cases[test_case['test']] ||= test_case.dup
349
- test_case.keys.select {|k| k =~ /^http:/}.each do |ts_uri|
350
- tc_desc[ts_uri] = test_case[ts_uri]['@id']
351
- assertions << test_case[ts_uri]
352
- end
353
- end
354
-
355
394
  # Write out each earl:TestSubject
356
395
  io.puts %(#\n# Subject Definitions\n#)
357
396
  json_hash['testSubjects'].each do |ts_desc|
@@ -360,14 +399,8 @@ class EarlReport
360
399
 
361
400
  # Write out each earl:TestCase
362
401
  io.puts %(#\n# Test Case Definitions\n#)
363
- test_cases.keys.sort.each do |num|
364
- io.write(tc_turtle(test_cases[num]))
365
- end
366
-
367
- # Write out each earl:Assertion
368
- io.puts %(#\n# Assertions\n#)
369
- assertions.sort_by {|a| a['@id']}.each do |as_desc|
370
- io.write(as_turtle(as_desc))
402
+ json_hash['tests'].each do |test_case|
403
+ io.write(tc_turtle(test_case))
371
404
  end
372
405
  end
373
406
 
@@ -376,19 +409,24 @@ class EarlReport
376
409
  # @param [Hash] desc
377
410
  # @return [String]
378
411
  def test_subject_turtle(desc)
379
- developer = desc['developer']
380
412
  res = %(<#{desc['@id']}> a #{desc['@type'].join(', ')};\n)
381
413
  res += %( doap:name "#{desc['name']}";\n)
382
- res += %( doap:description """#{desc['doap_desc']}""";\n) if desc['doap_desc']
414
+ res += %( doap:description """#{desc['doapDesc']}""";\n) if desc['doapDesc']
383
415
  res += %( doap:programming-language "#{desc['language']}";\n) if desc['language']
384
- if developer && developer['@id']
385
- res += %( doap:developer <#{developer['@id']}> .\n\n)
386
- res += %(<#{developer['@id']}> a #{[developer['@type']].flatten.join(', ')};\n)
387
- res += %( foaf:name "#{developer['foaf:name']}" .\n)
388
- elsif developer
389
- res += %( doap:developer [ a #{developer['@type'] || "foaf:Person"}; foaf:name "#{developer['foaf:name']}"] .\n)
390
- else
391
- res += %( .\n)
416
+ res += %( .\n\n)
417
+
418
+ [desc['developer']].flatten.each do |developer|
419
+ if developer['@id']
420
+ res += %(<#{desc['@id']}> doap:developer <#{developer['@id']}> .\n\n)
421
+ res += %(<#{developer['@id']}> a #{[developer['@type']].flatten.join(', ')};\n)
422
+ res += %( foaf:homepage <#{developer['foaf:homepage']}>;\n) if developer['foaf:homepage']
423
+ res += %( foaf:name "#{developer['foaf:name']}" .\n\n)
424
+ else
425
+ res += %(<#{desc['@id']}> doap:developer\n)
426
+ res += %( [ a #{developer['@type'] || "foaf:Person"};\n)
427
+ res += %( foaf:homepage <#{developer['foaf:homepage']}>;\n) if developer['foaf:homepage']
428
+ res += %( foaf:name "#{developer['foaf:name']}" ] .\n\n)
429
+ end
392
430
  end
393
431
  res + "\n"
394
432
  end
@@ -398,12 +436,16 @@ class EarlReport
398
436
  # @prarm[Hash] desc
399
437
  # @return [String]
400
438
  def tc_turtle(desc)
401
- res = %(<#{desc['@id']}> a #{[desc['@type']].flatten.join(', ')};\n)
402
- res += %( dc:title "#{desc['title']}";\n)
403
- res += %( dc:description """#{desc['description']}""";\n)
404
- res += %( mf:action <#{desc['testAction']}>;\n)
405
- res += %( mf:result <#{desc['testResult']}>;\n)
406
- res + "\n"
439
+ res = %{#{as_resource desc['@id']} a #{[desc['@type']].flatten.join(', ')};\n}
440
+ res += %{ dc:title "#{desc['title']}";\n}
441
+ res += %{ dc:description """#{desc['description']}""";\n} if desc.has_key?('description')
442
+ res += %{ mf:result #{as_resource desc['testResult']};\n} if desc.has_key?('testResult')
443
+ res += %{ mf:action #{as_resource desc['testAction']};\n}
444
+ res += %{ earl:assertions (\n}
445
+ desc['assertions'].each do |as_desc|
446
+ res += as_turtle(as_desc)
447
+ end
448
+ res += %{ ) .\n\n}
407
449
  end
408
450
 
409
451
  ##
@@ -411,16 +453,18 @@ class EarlReport
411
453
  # @prarm[Hash] desc
412
454
  # @return [String]
413
455
  def as_turtle(desc)
414
- res = %([ a earl:Assertion;\n)
415
- res += %( earl:assertedBy <#{desc['assertedBy']}>;\n)
416
- res += %( earl:test <#{desc['test']}>;\n)
417
- res += %( earl:subject <#{desc['subject']}>;\n)
418
- res += %( earl:mode #{desc['mode']};\n)
419
- res += %( earl:result [ a earl:TestResult; earl:outcome #{desc['result']['outcome']}] ] .\n)
420
- res += %(\n)
421
- res
456
+ res = %( [ a earl:Assertion;\n)
457
+ res += %( earl:assertedBy #{as_resource desc['assertedBy']};\n) if desc['assertedBy']
458
+ res += %( earl:test #{as_resource desc['test']};\n)
459
+ res += %( earl:subject #{as_resource desc['subject']};\n)
460
+ res += %( earl:mode #{desc['mode']};\n) if desc['mode']
461
+ res += %( earl:result [ a earl:TestResult; earl:outcome #{desc['result']['outcome']} ]]\n)
422
462
  end
423
463
 
464
+ def as_resource(resource)
465
+ resource[0,2] == '_:' ? resource : "<#{resource}>"
466
+ end
467
+
424
468
  def status(message)
425
469
  puts message if @options[:verbose]
426
470
  end