1+ const { getJsonFromXMLFile } = require ( '../helpers/helper' ) ;
2+
3+ const TestResult = require ( '../models/TestResult' ) ;
4+ const TestSuite = require ( '../models/TestSuite' ) ;
5+ const TestCase = require ( '../models/TestCase' ) ;
6+
7+ const SUITE_TYPES_WITH_TESTCASES = [
8+ "TestFixture" ,
9+ "ParameterizedTest" ,
10+ "GenericFixture" ,
11+ "ParameterizedMethod" // v3
12+ ]
13+
14+ const RESULTMAP = {
15+ Success : "PASS" , // v2
16+ Failure : "FAIL" , // v2
17+ Ignored : "SKIP" , // v2
18+ NotRunnable : "SKIP" , // v2
19+ Error : "ERROR" , // v2
20+ Inconclusive : "FAIL" , // v2
21+
22+ Passed : "PASS" , // v3
23+ Failed : "FAIL" , // v3
24+ Skipped : "SKIP" , // v3
25+ }
26+
27+ function mergeMeta ( map1 , map2 ) {
28+ for ( let kvp of map1 ) {
29+ map2 . set ( kvp [ 0 ] , kvp [ 1 ] ) ;
30+ }
31+ }
32+
33+ function populateMetaData ( raw , map ) {
34+
35+ // v2 supports categories
36+ if ( raw . categories ) {
37+ let categories = raw . categories . category ;
38+ for ( let i = 0 ; i < categories . length ; i ++ ) {
39+ let categoryName = categories [ i ] [ "@_name" ] ;
40+ map . set ( categoryName , "" ) ;
41+
42+ // create comma-delimited list of categories
43+ if ( map . has ( "Categories" ) ) {
44+ map . set ( "Categories" , map . get ( "Categories" ) . concat ( "," , categoryName ) ) ;
45+ } else {
46+ map . set ( "Categories" , categoryName ) ;
47+ }
48+ }
49+ }
50+
51+ // v2/v3 support properties
52+ if ( raw . properties ) {
53+ let properties = raw . properties . property ;
54+ for ( let i = 0 ; i < properties . length ; i ++ ) {
55+ let property = properties [ i ] ;
56+ let propName = property [ "@_name" ] ;
57+ let propValue = property [ "@_value" ] ;
58+
59+ // v3 treats 'Categories' as property "Category"
60+ if ( propName == "Category" ) {
61+
62+ if ( map . has ( "Categories" ) ) {
63+ map . set ( "Categories" , map . get ( "Categories" ) . concat ( "," , propValue ) ) ;
64+ } else {
65+ map . set ( "Categories" , propValue ) ;
66+ }
67+ map . set ( propValue , "" ) ;
68+
69+ } else {
70+ map . set ( propName , propValue ) ;
71+ }
72+ }
73+ }
74+ }
75+
76+ function getNestedTestCases ( rawSuite ) {
77+ if ( rawSuite . results ) {
78+ return rawSuite . results [ "test-case" ] ;
79+ } else {
80+ return rawSuite [ "test-case" ] ;
81+ }
82+ }
83+
84+ function hasNestedSuite ( rawSuite ) {
85+ return getNestedSuite ( rawSuite ) !== null ;
86+ }
87+
88+ function getNestedSuite ( rawSuite ) {
89+ // nunit v2 nests test-suite inside 'results'
90+ if ( rawSuite . results && rawSuite . results [ "test-suite" ] ) {
91+ return rawSuite . results [ "test-suite" ] ;
92+ } else {
93+ // nunit v3 nests test-suites as immediate children
94+ if ( rawSuite [ "test-suite" ] ) {
95+ return rawSuite [ "test-suite" ] ;
96+ }
97+ else {
98+ // not nested
99+ return null ;
100+ }
101+ }
102+ }
103+
104+ function getTestCases ( rawSuite , parent_meta ) {
105+ var cases = [ ] ;
106+
107+ let rawTestCases = getNestedTestCases ( rawSuite ) ;
108+ if ( rawTestCases ) {
109+ for ( let i = 0 ; i < rawTestCases . length ; i ++ ) {
110+ let rawCase = rawTestCases [ i ] ;
111+ let testCase = new TestCase ( ) ;
112+ let result = rawCase [ "@_result" ]
113+ testCase . id = rawCase [ "@_id" ] ?? "" ;
114+ testCase . name = rawCase [ "@_fullname" ] ?? rawCase [ "@_name" ] ;
115+ testCase . duration = rawCase [ "@_time" ] * 1000 ; // in milliseconds
116+ testCase . status = RESULTMAP [ result ] ;
117+
118+ // v2 : non-executed should be tests should be Ignored
119+ if ( rawCase [ "@_executed" ] == "False" ) {
120+ testCase . status = "SKIP" ; // exclude failures that weren't executed.
121+ }
122+ // v3 : failed tests with error label should be Error
123+ if ( rawCase [ "@_label" ] == "Error" ) {
124+ testCase . status = "ERROR" ;
125+ }
126+ let errorDetails = rawCase . reason ?? rawCase . failure ;
127+ if ( errorDetails !== undefined ) {
128+ testCase . setFailure ( errorDetails . message ) ;
129+ if ( errorDetails [ "stack-trace" ] ) {
130+ testCase . stack_trace = errorDetails [ "stack-trace" ]
131+ }
132+ }
133+ // copy parent_meta data to test case
134+ mergeMeta ( parent_meta , testCase . meta_data ) ;
135+ populateMetaData ( rawCase , testCase . meta_data ) ;
136+
137+ cases . push ( testCase ) ;
138+ }
139+ }
140+
141+ return cases ;
142+ }
143+
144+ function getTestSuites ( rawSuites , assembly_meta ) {
145+ var suites = [ ] ;
146+
147+ for ( let i = 0 ; i < rawSuites . length ; i ++ ) {
148+ let rawSuite = rawSuites [ i ] ;
149+
150+ if ( rawSuite [ "@_type" ] == "Assembly" ) {
151+ assembly_meta = new Map ( ) ;
152+ populateMetaData ( rawSuite , assembly_meta ) ;
153+ }
154+
155+ if ( hasNestedSuite ( rawSuite ) ) {
156+ // handle nested test-suites
157+ suites . push ( ...getTestSuites ( getNestedSuite ( rawSuite ) , assembly_meta ) ) ;
158+ } else if ( SUITE_TYPES_WITH_TESTCASES . indexOf ( rawSuite [ "@_type" ] ) !== - 1 ) {
159+
160+ let suite = new TestSuite ( ) ;
161+ suite . id = rawSuite [ "@_id" ] ?? '' ;
162+ suite . name = rawSuite [ "@_fullname" ] ?? rawSuite [ "@_name" ] ;
163+ suite . duration = rawSuite [ "@_time" ] * 1000 ; // in milliseconds
164+ suite . status = RESULTMAP [ rawSuite [ "@_result" ] ] ;
165+
166+ var meta_data = new Map ( ) ;
167+ mergeMeta ( assembly_meta , meta_data ) ;
168+ populateMetaData ( rawSuite , meta_data ) ;
169+ suite . cases . push ( ...getTestCases ( rawSuite , meta_data ) ) ;
170+
171+ // calculate totals
172+ suite . total = suite . cases . length ;
173+ suite . passed = suite . cases . filter ( i => i . status == "PASS" ) . length ;
174+ suite . failed = suite . cases . filter ( i => i . status == "FAIL" ) . length ;
175+ suite . errors = suite . cases . filter ( i => i . status == "ERROR" ) . length ;
176+ suite . skipped = suite . cases . filter ( i => i . status == "SKIP" ) . length ;
177+
178+ suites . push ( suite ) ;
179+ }
180+ }
181+
182+ return suites ;
183+ }
184+
185+
186+ function getTestResult ( json ) {
187+ const nunitVersion = ( json [ "test-results" ] !== undefined ) ? "v2" :
188+ ( json [ "test-run" ] !== undefined ) ? "v3" : null ;
189+
190+ if ( nunitVersion == null ) {
191+ throw new Error ( "Unrecognized xml format" ) ;
192+ }
193+
194+ const result = new TestResult ( ) ;
195+ const rawResult = json [ "test-results" ] ?? json [ "test-run" ] ;
196+ const rawSuite = rawResult [ "test-suite" ] [ 0 ] ;
197+
198+ result . name = rawResult [ "@_fullname" ] ?? rawResult [ "@_name" ] ;
199+ result . duration = rawSuite [ "@_time" ] * 1000 ; // in milliseconds
200+
201+ result . suites . push ( ...getTestSuites ( [ rawSuite ] , null ) ) ;
202+
203+ result . total = result . suites . reduce ( ( total , suite ) => { return total + suite . cases . length } , 0 ) ;
204+ result . passed = result . suites . reduce ( ( total , suite ) => { return total + suite . passed } , 0 ) ;
205+ result . failed = result . suites . reduce ( ( total , suite ) => { return total + suite . failed } , 0 ) ;
206+ result . skipped = result . suites . reduce ( ( total , suite ) => { return total + suite . skipped } , 0 ) ;
207+ result . errors = result . suites . reduce ( ( total , suite ) => { return total + suite . errors } , 0 ) ;
208+
209+ return result ;
210+ }
211+
212+ function parse ( file ) {
213+ const json = getJsonFromXMLFile ( file ) ;
214+ return getTestResult ( json ) ;
215+ }
216+
217+ module . exports = {
218+ parse
219+ }
0 commit comments