diff options
Diffstat (limited to 'valadate/testreport.vala')
-rw-r--r-- | valadate/testreport.vala | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/valadate/testreport.vala b/valadate/testreport.vala new file mode 100644 index 000000000..8e4dc734b --- /dev/null +++ b/valadate/testreport.vala @@ -0,0 +1,278 @@ +/* + * Valadate - Unit testing library for GObject-based libraries. + * Copyright (C) 2017 Chris Daley <chebizarro@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Chris Daley <chebizarro@gmail.com> + */ + +public class Valadate.TestReport { + + private const string XML_DECL ="<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + private const string TESTSUITE_XML = + """<testsuite disabled="" errors="" failures="" hostname="" id="" """ + + """name="" package="" skipped="" tests="" time="" timestamp="" >"""+ + """<properties/></testsuite>"""; + private const string TESTCASE_XML = + """<testcase assertions="" classname="" name="" status="" time="" />"""; + private const string MESSAGE_XML = "<%s message=\"%s\" type=\"%s\">%s</%s>"; + private const string TESTCASE_START = + "<testcase assertions=\"\" classname=\"%s\" name=\"%s\" status=\"\" time=\"\">"; + private const string VDX_NS = "xmlns:vdx=\"https://www.valadate.org/vdx\""; + private const string TESTCASE_TAG = "testcase"; + private const string ROOT_TAG = "root"; + private const string SKIP_TAG = "skipped"; + private const string ERROR_TAG = "error"; + private const string FAILURE_TAG = "failure"; + private const string INFO_TAG = "info"; + private const string TIMER_TAG = "timer"; + private const string SYSTEM_OUT_TAG = "system-out"; + private const string SYSTEM_ERR_TAG = "system-err"; + + public Test test {get;set;} + public bool subprocess {get;set;} + + public XmlFile xml {get;set;} + + private static int64 start_time; + private static int64 end_time; + + private static Regex regex_err; + private const string regex_err_string = + """(\*{2}\n([A-Z]*):([\S]*) ([\S ]*)\n)"""; + + public TestReport(Test test, bool subprocess) throws Error { + this.test = test; + this.subprocess = subprocess; + + if(test.status == TestStatus.NOT_RUN) + test.status = TestStatus.RUNNING; + + if(subprocess) { + Log.set_default_handler (log_func); + GLib.set_printerr_handler (printerr_func); + regex_err = new Regex(regex_err_string); + } + + if(test is TestSuite || test is TestCase) + new_testsuite(); + else if (test is TestAdapter) + new_testcase(); + } + + private void new_testsuite() throws Error { + if(subprocess) + return; + + var decl = "%s<%s>%s</%s>".printf(XML_DECL, ROOT_TAG, TESTSUITE_XML, ROOT_TAG); + var doc = Xml.Parser.read_memory(decl, decl.length); + var root = doc->get_root_element()->children; + root->set_prop("tests", test.count.to_string()); + root->set_prop("name",test.label); + xml = new XmlFile.from_doc(doc); + + if(test.parent != null && test.parent.name != "/") + return; + + var props = root->children; + + foreach(var key in Environment.list_variables()) { + Xml.Node* node = new Xml.Node(null, "property"); + node->set_prop("name", key); + node->set_prop("value", Markup.escape_text(Environment.get_variable(key))); + props->add_child(node); + } + } + + private void new_testcase() throws Error { + if(subprocess) { + stderr.printf("%s<%s>",XML_DECL,ROOT_TAG); + stderr.printf(TESTCASE_START,test.parent.get_type().name(), test.label); + start_time = get_monotonic_time(); + } else { + var decl = "%s<%s>%s</%s>".printf(XML_DECL, ROOT_TAG, TESTCASE_XML, ROOT_TAG); + var doc = Xml.Parser.read_memory(decl, decl.length); + var root = doc->get_root_element()->children; + root->set_prop("classname",((TestAdapter)test).parent.name); + root->set_prop("status",test.status.to_string().substring(21)); + root->set_prop("name",test.label); + xml = new XmlFile.from_doc(doc); + } + } + + public void add_error(string message) { + if (test.status != TestStatus.SKIPPED && + test.status != TestStatus.TODO) + test.status = TestStatus.ERROR; + + add_message(ERROR_TAG, message); + + if(subprocess) { + emit_timer(); + stderr.printf("</%s></%s>",TESTCASE_TAG, ROOT_TAG); + stderr.putc(0); + } + update_status(); + } + + public void add_failure(string message) { + if (test.status != TestStatus.SKIPPED && + test.status != TestStatus.TODO) + test.status = TestStatus.FAILED; + + add_message(FAILURE_TAG, message); + + if(subprocess) { + emit_timer(); + stderr.printf("</%s></%s>",TESTCASE_TAG, ROOT_TAG); + stderr.putc(0); + } + update_status(); + } + + public void add_skip(string message) { + test.status = TestStatus.SKIPPED; + add_message(SKIP_TAG, message); + update_status(); + } + + public void add_success() { + if (test.status != TestStatus.SKIPPED && + test.status != TestStatus.TODO) + test.status = TestStatus.PASSED; + if(subprocess) { + emit_timer(); + stderr.printf("</%s></%s>",TESTCASE_TAG, ROOT_TAG); + stderr.putc(0); + } + update_status(); + } + + private void add_message(string tag, string message) { + var escaped = Markup.escape_text(message); + if(subprocess) { + stderr.printf(MESSAGE_XML, tag, escaped, tag.up(), message, tag); + } else { + Xml.Node* child = new Xml.Node(null, tag); + child->set_content(escaped); + + string[] tags = {ERROR_TAG, FAILURE_TAG, INFO_TAG}; + + if(tag in tags) { + child->new_prop("message", escaped); + child->new_prop("type", tag.up()); + } + + Xml.Node* root = xml.eval("//testcase | //testsuite")[0]; + root->add_child(child); + } + } + /** + * Adds arbitrary text to the TestReport. In the xml output this + * text will be encapsulated in <system-out/> or <system-err/> tag + * + * @param text The text to be added to the {@link TestReport}. + * the text will be escaped before being added. + * @param tag The tag to use for adding the text + */ + public void add_text(string text, string tag) { + var markup = Markup.escape_text(text); + Xml.Node* child = new Xml.Node(null, tag); + child->set_content(markup); + + string[] tags = {ERROR_TAG, FAILURE_TAG, INFO_TAG}; + + if(tag in tags) { + child->new_prop("message", markup); + child->new_prop("type", tag.up()); + } + + Xml.Node* root = xml.eval("//testcase | //testsuite")[0]; + root->add_child(child); + } + + public void update_status() { + if(test is TestAdapter && !subprocess) { + Xml.Node* root = xml.eval("//testcase")[0]; + root->set_prop("status",test.status.to_string().substring(21)); + root->set_prop("time",test.time.to_string()); + } + } + + private static void emit_timer() { + end_time = get_monotonic_time(); + var ms = "%f".printf(((double)(end_time-start_time))/1000); + stderr.printf(MESSAGE_XML, TIMER_TAG, ms, TIMER_TAG, ms, TIMER_TAG); + } + + private static void printerr_func (string? text) { + if(text == null) + return; + MatchInfo info; + if(regex_err.match(text, 0, out info)) { + var escaped = Markup.escape_text(info.fetch(4)); + stderr.printf(MESSAGE_XML, ERROR_TAG, escaped, ERROR_TAG, text, ERROR_TAG); + emit_timer(); + stderr.printf("</%s></%s>",TESTCASE_TAG, ROOT_TAG); + stderr.putc(0); + } + } + + private void log_func ( + string? log_domain, + LogLevelFlags log_levels, + string? message) { + + if (((log_levels & LogLevelFlags.LEVEL_INFO) != 0) || + ((log_levels & LogLevelFlags.LEVEL_MESSAGE) != 0) || + ((log_levels & LogLevelFlags.LEVEL_DEBUG) != 0)) { + add_message(INFO_TAG, message); + } else { + add_error(message); + } + } + + public void process_buffer(string buffer) throws Error { + + xml = new XmlFile.from_string(buffer); + + var bits = xml.eval("//testcase/text()"); + + if(bits.size != 0) { + Xml.Node* textnode = bits[0]; + add_message(SYSTEM_ERR_TAG, textnode->get_content()); + textnode->unlink(); + } + + var errs = xml.eval("//failure | //error"); + if (errs.size > 0 && + test.status != TestStatus.SKIPPED && + test.status != TestStatus.TODO) + test.status = TestStatus.FAILED; + + bits = xml.eval("//timer"); + Xml.Node* timer = bits[0]; + test.time = double.parse(timer->get_content()); + timer->unlink(); + + update_status(); + } + + public void add_stdout(string text) { + add_message(SYSTEM_OUT_TAG, text); + } +} |