102030405060708090100110120130140150160170180190200210220230240250260270280290300310320330340350360370380390400410420430440450460470480490500510520530540550560570580590600610620630640650660670680690700710720730740750760770780790800810820830840850860870880890900910920930940950960970980990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940 module trial.reporters.allure; import std.stdio; import std.array; import std.conv; import std.datetime; import std.string; import std.algorithm; import std.file; import std.path; import std.uuid; import std.range; import trial.interfaces; import trial.reporters.writer; private string escape(string data) { string escapedData = data.dup; escapedData = escapedData.replace(`&`, `&`); escapedData = escapedData.replace(`"`, `"`); escapedData = escapedData.replace(`'`, `'`); escapedData = escapedData.replace(`<`, `<`); escapedData = escapedData.replace(`>`, `>`); return escapedData; } class AllureReporter : ILifecycleListener { private { immutable string destination; } this(string destination) { this.destination = destination; } void begin(ulong testCount) { if(exists(destination)) { std.file.rmdirRecurse(destination); } } void update() {} void end(SuiteResult[] result) { if(!exists(destination)) { destination.mkdirRecurse; } foreach(item; result) { string uuid = randomUUID.toString; string xml = AllureSuiteXml(destination, item, uuid).toString; std.file.write(buildPath(destination, uuid ~ "-testsuite.xml"), xml); } } } struct AllureSuiteXml { SuiteResult result; string uuid; const string allureVersion = "1.5.2"; private { immutable string destination; } this(const string destination, SuiteResult result, string uuid) { this.destination = destination; this.result = result; this.uuid = uuid; } string toString() { auto epoch = SysTime.fromUnixTime(0); string tests = result.tests.map!(a => AllureTestXml(destination, a, uuid).toString).array.join("\n"); if(tests != "") { tests = "\n" ~ tests; } auto xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:test-suite start="` ~ (result.begin - epoch).total!"msecs".to!string ~ `" stop="` ~ (result.end - epoch).total!"msecs".to!string ~ `" version="` ~ this.allureVersion ~ `" xmlns:ns2="urn:model.allure.qatools.yandex.ru"> <name>` ~ result.name.escape ~ `</name> <title>` ~ result.name.escape ~ `</title> <test-cases>` ~ tests ~ ` </test-cases> `; if(result.attachments.length > 0) { xml ~= " <attachments>\n"; xml ~= result.attachments.map!(a => AllureAttachmentXml(destination, a, 6, uuid)).map!(a => a.toString).array.join('\n') ~ "\n"; xml ~= " </attachments>\n"; } xml ~= ` <labels> <label name="framework" value="Trial"/> <label name="language" value="D"/> </labels> </ns2:test-suite>`; return xml; } } struct AllureTestXml { TestResult result; string uuid; private { immutable string destination; } this(const string destination, TestResult result, string uuid) { this.destination = destination; this.result = result; this.uuid = uuid; } string allureStatus() { switch(result.status) { case TestResult.Status.created: return "canceled"; case TestResult.Status.failure: return "failed"; case TestResult.Status.skip: return "canceled"; case TestResult.Status.success: return "passed"; default: return "unknown"; } } string toString() { auto epoch = SysTime.fromUnixTime(0); auto start = (result.begin - epoch).total!"msecs"; auto stop = (result.end - epoch).total!"msecs"; string xml = ` <test-case start="` ~ start.to!string ~ `" stop="` ~ stop.to!string ~ `" status="` ~ allureStatus ~ `">` ~ "\n"; xml ~= ` <name>` ~ result.name.escape ~ `</name>` ~ "\n"; if(result.labels.length > 0) { xml ~= " <labels>\n"; foreach(label; result.labels) { xml ~= " <label name=\"" ~ label.name ~ "\" value=\"" ~ label.value ~ "\"/>\n"; } xml ~= " </labels>\n"; } if(result.throwable !is null) { xml ~= ` <failure> <message>` ~ result.throwable.msg.escape ~ `</message> <stack-trace>` ~ result.throwable.to!string.escape ~ `</stack-trace> </failure>` ~ "\n"; } if(result.steps.length > 0) { xml ~= " <steps>\n"; xml ~= result.steps.map!(a => AllureStepXml(destination, a, 14, uuid)).map!(a => a.toString).array.join('\n') ~ "\n"; xml ~= " </steps>\n"; } if(result.attachments.length > 0) { xml ~= " <attachments>\n"; xml ~= result.attachments.map!(a => AllureAttachmentXml(destination, a, 14, uuid)).map!(a => a.toString).array.join('\n') ~ "\n"; xml ~= " </attachments>\n"; } xml ~= ` </test-case>`; return xml; } } struct AllureStepXml { private { StepResult step; size_t indent; string uuid; immutable string destination; } this(const string destination, StepResult step, size_t indent, string uuid) { this.step = step; this.indent = indent; this.uuid = uuid; this.destination = destination; } string toString() { auto epoch = SysTime.fromUnixTime(0); const spaces = " " ~ (" ".repeat(indent).array.join()); string result = spaces ~ `<step start="` ~ (step.begin - epoch).total!"msecs".to!string ~ `" stop="` ~ (step.end - epoch).total!"msecs".to!string ~ `" status="passed">` ~ "\n" ~ spaces ~ ` <name>` ~ step.name.escape ~ `</name>` ~ "\n"; if(step.steps.length > 0) { result ~= spaces ~ " <steps>\n"; result ~= step.steps.map!(a => AllureStepXml(destination, a, indent + 6, uuid)).map!(a => a.to!string).array.join('\n') ~ "\n"; result ~= spaces ~ " </steps>\n"; } if(step.attachments.length > 0) { result ~= spaces ~ " <attachments>\n"; result ~= step.attachments.map!(a => AllureAttachmentXml(destination, a, indent + 6, uuid)).map!(a => a.to!string).array.join('\n') ~ "\n"; result ~= spaces ~ " </attachments>\n"; } result ~= spaces ~ `</step>`; return result; } } struct AllureAttachmentXml { private { const { Attachment attachment; size_t indent; } string allureFile; } @disable this(); this(const string destination, Attachment attachment, size_t indent, string uuid) { this.indent = indent; if(!exists(buildPath(destination, uuid))) { buildPath(destination, uuid).mkdirRecurse; } ulong index; do { allureFile = buildPath(uuid, attachment.name ~ "." ~ index.to!string ~ "." ~ baseName(attachment.file)); index++; } while(buildPath(destination, allureFile).exists); if(attachment.file.exists) { std.file.copy(attachment.file, buildPath(destination, allureFile)); } this.attachment = Attachment(attachment.name, buildPath(destination, allureFile), attachment.mime); } string toString() { return (" ".repeat(indent).array.join()) ~ "<attachment title=\"" ~ attachment.name ~ "\" source=\"" ~ allureFile ~ "\" type=\"" ~ attachment.mime ~ "\" />"; } }
module trial.reporters.allure; import std.stdio; import std.array; import std.conv; import std.datetime; import std.string; import std.algorithm; import std.file; import std.path; import std.uuid; import std.range; import trial.interfaces; import trial.reporters.writer; private string escape(string data) { string escapedData = data.dup; escapedData = escapedData.replace(`&`, `&`); escapedData = escapedData.replace(`"`, `"`); escapedData = escapedData.replace(`'`, `'`); escapedData = escapedData.replace(`<`, `<`); escapedData = escapedData.replace(`>`, `>`); return escapedData; } class AllureReporter : ILifecycleListener { private { immutable string destination; } this(string destination) { this.destination = destination; } void begin(ulong testCount) { if(exists(destination)) { std.file.rmdirRecurse(destination); } } void update() {} void end(SuiteResult[] result) { if(!exists(destination)) { destination.mkdirRecurse; } foreach(item; result) { string uuid = randomUUID.toString; string xml = AllureSuiteXml(destination, item, uuid).toString; std.file.write(buildPath(destination, uuid ~ "-testsuite.xml"), xml); } } } struct AllureSuiteXml { SuiteResult result; string uuid; const string allureVersion = "1.5.2"; private { immutable string destination; } this(const string destination, SuiteResult result, string uuid) { this.destination = destination; this.result = result; this.uuid = uuid; } string toString() { auto epoch = SysTime.fromUnixTime(0); string tests = result.tests.map!(a => AllureTestXml(destination, a, uuid).toString).array.join("\n"); if(tests != "") { tests = "\n" ~ tests; } auto xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:test-suite start="` ~ (result.begin - epoch).total!"msecs".to!string ~ `" stop="` ~ (result.end - epoch).total!"msecs".to!string ~ `" version="` ~ this.allureVersion ~ `" xmlns:ns2="urn:model.allure.qatools.yandex.ru"> <name>` ~ result.name.escape ~ `</name> <title>` ~ result.name.escape ~ `</title> <test-cases>` ~ tests ~ ` </test-cases> `; if(result.attachments.length > 0) { xml ~= " <attachments>\n"; xml ~= result.attachments.map!(a => AllureAttachmentXml(destination, a, 6, uuid)).map!(a => a.toString).array.join('\n') ~ "\n"; xml ~= " </attachments>\n"; } xml ~= ` <labels> <label name="framework" value="Trial"/> <label name="language" value="D"/> </labels> </ns2:test-suite>`; return xml; } } struct AllureTestXml { TestResult result; string uuid; private { immutable string destination; } this(const string destination, TestResult result, string uuid) { this.destination = destination; this.result = result; this.uuid = uuid; } string allureStatus() { switch(result.status) { case TestResult.Status.created: return "canceled"; case TestResult.Status.failure: return "failed"; case TestResult.Status.skip: return "canceled"; case TestResult.Status.success: return "passed"; default: return "unknown"; } } string toString() { auto epoch = SysTime.fromUnixTime(0); auto start = (result.begin - epoch).total!"msecs"; auto stop = (result.end - epoch).total!"msecs"; string xml = ` <test-case start="` ~ start.to!string ~ `" stop="` ~ stop.to!string ~ `" status="` ~ allureStatus ~ `">` ~ "\n"; xml ~= ` <name>` ~ result.name.escape ~ `</name>` ~ "\n"; if(result.labels.length > 0) { xml ~= " <labels>\n"; foreach(label; result.labels) { xml ~= " <label name=\"" ~ label.name ~ "\" value=\"" ~ label.value ~ "\"/>\n"; } xml ~= " </labels>\n"; } if(result.throwable !is null) { xml ~= ` <failure> <message>` ~ result.throwable.msg.escape ~ `</message> <stack-trace>` ~ result.throwable.to!string.escape ~ `</stack-trace> </failure>` ~ "\n"; } if(result.steps.length > 0) { xml ~= " <steps>\n"; xml ~= result.steps.map!(a => AllureStepXml(destination, a, 14, uuid)).map!(a => a.toString).array.join('\n') ~ "\n"; xml ~= " </steps>\n"; } if(result.attachments.length > 0) { xml ~= " <attachments>\n"; xml ~= result.attachments.map!(a => AllureAttachmentXml(destination, a, 14, uuid)).map!(a => a.toString).array.join('\n') ~ "\n"; xml ~= " </attachments>\n"; } xml ~= ` </test-case>`; return xml; } } struct AllureStepXml { private { StepResult step; size_t indent; string uuid; immutable string destination; } this(const string destination, StepResult step, size_t indent, string uuid) { this.step = step; this.indent = indent; this.uuid = uuid; this.destination = destination; } string toString() { auto epoch = SysTime.fromUnixTime(0); const spaces = " " ~ (" ".repeat(indent).array.join()); string result = spaces ~ `<step start="` ~ (step.begin - epoch).total!"msecs".to!string ~ `" stop="` ~ (step.end - epoch).total!"msecs".to!string ~ `" status="passed">` ~ "\n" ~ spaces ~ ` <name>` ~ step.name.escape ~ `</name>` ~ "\n"; if(step.steps.length > 0) { result ~= spaces ~ " <steps>\n"; result ~= step.steps.map!(a => AllureStepXml(destination, a, indent + 6, uuid)).map!(a => a.to!string).array.join('\n') ~ "\n"; result ~= spaces ~ " </steps>\n"; } if(step.attachments.length > 0) { result ~= spaces ~ " <attachments>\n"; result ~= step.attachments.map!(a => AllureAttachmentXml(destination, a, indent + 6, uuid)).map!(a => a.to!string).array.join('\n') ~ "\n"; result ~= spaces ~ " </attachments>\n"; } result ~= spaces ~ `</step>`; return result; } } struct AllureAttachmentXml { private { const { Attachment attachment; size_t indent; } string allureFile; } @disable this(); this(const string destination, Attachment attachment, size_t indent, string uuid) { this.indent = indent; if(!exists(buildPath(destination, uuid))) { buildPath(destination, uuid).mkdirRecurse; } ulong index; do { allureFile = buildPath(uuid, attachment.name ~ "." ~ index.to!string ~ "." ~ baseName(attachment.file)); index++; } while(buildPath(destination, allureFile).exists); if(attachment.file.exists) { std.file.copy(attachment.file, buildPath(destination, allureFile)); } this.attachment = Attachment(attachment.name, buildPath(destination, allureFile), attachment.mime); } string toString() { return (" ".repeat(indent).array.join()) ~ "<attachment title=\"" ~ attachment.name ~ "\" source=\"" ~ allureFile ~ "\" type=\"" ~ attachment.mime ~ "\" />"; } }