# The XML node associated to the module
var testsuite: HTMLTag
- # All blocks of code from a same `ADoc`
- var blocks = new Array[Buffer]
+ # The current test-case xml element
+ var tc: HTMLTag is noautoinit
# All failures from a same `ADoc`
var failures = new Array[String]
# used to generate distinct names
var cpt = 0
+ # The last docunit extracted from a mdoc.
+ #
+ # Is used because a new code-block might just be added to it.
+ var last_docunit: nullable DocUnit = null
+
# The entry point for a new `ndoc` node
# Fill `docunits` with new discovered unit of tests.
#
# `tc` (testcase) is the pre-filled XML node
fun extract(mdoc: MDoc, tc: HTMLTag)
do
- blocks.clear
+ last_docunit = null
failures.clear
+ self.tc = tc
self.mdoc = mdoc
toolcontext.modelbuilder.unit_entities += 1
toolcontext.modelbuilder.failed_entities += 1
end
- if blocks.is_empty then testsuite.add(tc)
- end
-
- if blocks.is_empty then return
- for block in blocks do
- docunits.add new DocUnit(mdoc, tc, block.write_to_string)
+ if last_docunit == null then testsuite.add(tc)
end
end
end
test_simple_docunits(simple_du)
+
+ for du in docunits do
+ testsuite.add du.to_xml
+ end
end
# Executes multiples doc-units in a shared program.
toolcontext.modelbuilder.unit_entities += 1
i += 1
toolcontext.info("Execute doc-unit {du.testcase.attrs["name"]} in {file} {i}", 1)
- var res2 = sys.system("{file.to_program_name}.bin {i} >>'{file}.out1' 2>&1 </dev/null")
-
- var msg
- f = new FileReader.open("{file}.out1")
- var n2
- n2 = new HTMLTag("system-err")
- tc.add n2
- msg = f.read_all
- f.close
+ var res2 = toolcontext.safe_exec("{file.to_program_name}.bin {i} >'{file}.out1' 2>&1 </dev/null")
- n2 = new HTMLTag("system-out")
- tc.add n2
- n2.append(du.block)
+ var content = "{file}.out1".to_path.read_all
+ var msg = content.trunc(8192).filter_nonprintable
if res2 != 0 then
- var ne = new HTMLTag("error")
- ne.attr("message", msg)
- tc.add ne
- toolcontext.warning(du.mdoc.location, "error", "ERROR: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
+ du.error = content
+ toolcontext.warning(du.location, "error", "ERROR: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): Runtime error\n{msg}")
toolcontext.modelbuilder.failed_entities += 1
end
toolcontext.check_errors
-
- testsuite.add(tc)
end
end
var res = compile_unitfile(file)
var res2 = 0
if res == 0 then
- res2 = sys.system("{file.to_program_name}.bin >>'{file}.out1' 2>&1 </dev/null")
+ res2 = toolcontext.safe_exec("{file.to_program_name}.bin >'{file}.out1' 2>&1 </dev/null")
end
- var msg
- f = new FileReader.open("{file}.out1")
- var n2
- n2 = new HTMLTag("system-err")
- tc.add n2
- msg = f.read_all
- f.close
-
- n2 = new HTMLTag("system-out")
- tc.add n2
- n2.append(du.block)
+ var content = "{file}.out1".to_path.read_all
+ var msg = content.trunc(8192).filter_nonprintable
if res != 0 then
- var ne = new HTMLTag("failure")
- ne.attr("message", msg)
- tc.add ne
- toolcontext.warning(du.mdoc.location, "failure", "FAILURE: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
+ du.error = content
+ toolcontext.warning(du.location, "failure", "FAILURE: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}):\n{msg}")
toolcontext.modelbuilder.failed_entities += 1
else if res2 != 0 then
- var ne = new HTMLTag("error")
- ne.attr("message", msg)
- tc.add ne
- toolcontext.warning(du.mdoc.location, "error", "ERROR: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
+ toolcontext.warning(du.location, "error", "ERROR: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}):\n{msg}")
toolcontext.modelbuilder.failed_entities += 1
end
toolcontext.check_errors
-
- testsuite.add(tc)
end
# Create and fill the header of a unit file `file`.
# Can terminate the program if the compiler is not found
private fun compile_unitfile(file: String): Int
do
- var nit_dir = toolcontext.nit_dir
- var nitc = nit_dir/"bin/nitc"
- if not nitc.file_exists then
- toolcontext.error(null, "Error: cannot find nitc. Set envvar NIT_DIR.")
- toolcontext.check_errors
- end
+ var nitc = toolcontext.find_nitc
var opts = new Array[String]
if mmodule != null then
opts.add "-I {mmodule.filepath.dirname}"
end
var cmd = "{nitc} --ignore-visibility --no-color '{file}' {opts.join(" ")} >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
- var res = sys.system(cmd)
+ var res = toolcontext.safe_exec(cmd)
return res
end
end
# Create a first block
# Or create a new block for modules that are more than a main part
- if executor.blocks.is_empty or ast isa AModule then
- executor.blocks.add(new Buffer)
+ var last_docunit = executor.last_docunit
+ if last_docunit == null or ast isa AModule then
+ last_docunit = new DocUnit(executor.mdoc.as(not null), executor.tc, "")
+ executor.docunits.add last_docunit
end
# Add it to the file
- executor.blocks.last.append code
+ last_docunit.block += code
+
+ # In order to retrieve precise positions,
+ # the real position of each line of the raw_content is stored.
+ # See `DocUnit::real_location`
+ line_offset -= loc.line_start - 1
+ for i in [loc.line_start..loc.line_end] do
+ last_docunit.lines.add i + line_offset
+ last_docunit.columns.add column_offset
+ end
end
end
-# A unit-test to run
+# A unit-test extracted from some documentation.
+#
+# A docunit is extracted from the code-blocks of mdocs.
+# Each mdoc can contains more than one docunit, and a single docunit can be made of more that a single code-block.
class DocUnit
+ super UnitTest
+
# The doc that contains self
var mdoc: MDoc
# The XML node that contains the information about the execution
var testcase: HTMLTag
- # The text of the code to execute
+ # The text of the code to execute.
+ #
+ # This is the verbatim content on one, or more, code-blocks from `mdoc`
var block: String
+
+ # For each line in `block`, the associated line in the mdoc
+ #
+ # Is used to give precise locations
+ var lines = new Array[Int]
+
+ # For each line in `block`, the associated column in the mdoc
+ #
+ # Is used to give precise locations
+ var columns = new Array[Int]
+
+ # The location of the whole docunit.
+ #
+ # If `self` is made of multiple code-blocks, then the location
+ # starts at the first code-books and finish at the last one, thus includes anything between.
+ var location: Location is lazy do
+ return new Location(mdoc.location.file, lines.first, lines.last+1, columns.first+1, 0)
+ end
+
+ # Compute the real location of a node on the `ast` based on `mdoc.location`
+ #
+ # The result is basically: ast_location + markdown location of the piece + mdoc.location
+ #
+ # The fun is that a single docunit can be made of various pieces of code blocks.
+ fun real_location(ast_location: Location): Location
+ do
+ var mdoc = self.mdoc
+ var res = new Location(mdoc.location.file, lines[ast_location.line_start-1],
+ lines[ast_location.line_end-1],
+ columns[ast_location.line_start-1] + ast_location.column_start,
+ columns[ast_location.line_end-1] + ast_location.column_end)
+ return res
+ end
+
+ redef fun to_xml
+ do
+ var res = super
+ res.open("system-out").append(block)
+ return res
+ end
+
end
redef class ModelBuilder