Skip to content
This repository was archived by the owner on Feb 5, 2024. It is now read-only.

Commit 3a0a66f

Browse files
authored
Merge pull request #32 from taskworld/dtinth/allure-report-ansi
2 parents 740f227 + ffa23fb commit 3a0a66f

File tree

7 files changed

+218
-112
lines changed

7 files changed

+218
-112
lines changed

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ jobs:
2929
command: ALLURE_SUITE_NAME=prescript-examples yarn run test-examples
3030
- run:
3131
name: allure
32+
when: always
3233
command: |
3334
mkdir -p /tmp/allure-report
34-
sudo npm install -g allure-commandline
35-
allure generate allure-results -o "/tmp/allure-report"
35+
yarn allure generate allure-results -o "/tmp/allure-report"
3636
- store_artifacts:
3737
path: /tmp/allure-report
3838
prefix: allure-report

examples/regression/tests/Issue27.js

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,56 @@
1-
const { action, pending } = require('../../..')
1+
const { action } = require('../../..')
22
const assert = require('assert')
3+
const expect = require('expect')
34
const fs = require('fs')
45
const glob = require('glob')
56
const { execFileSync } = require('child_process')
67

7-
// To fix in later PR
8-
pending()
9-
10-
action('Run test (it should fail)', async () => {
11-
let failed = false
8+
action('Clean the results directory', async () => {
129
execFileSync('rm', ['-rf', 'tmp/issue27-allure-results'])
13-
try {
14-
execFileSync(
15-
'./bin/prescript',
16-
[require.resolve('../fixtures/Issue27-AnsiColorTestFixture.js')],
17-
{
18-
env: {
19-
...process.env,
20-
ALLURE_RESULTS_DIR: 'tmp/issue27-allure-results',
21-
ALLURE_SUITE_NAME: 'prescript-regression-issue27',
22-
FORCE_COLOR: '1'
10+
})
11+
12+
for (let i = 1; i <= 3; i++) {
13+
action('Run test (it should fail)', async () => {
14+
let failed = false
15+
try {
16+
execFileSync(
17+
'./bin/prescript',
18+
[require.resolve('../fixtures/Issue27-AnsiColorTestFixture.js')],
19+
{
20+
env: {
21+
...process.env,
22+
ALLURE_RESULTS_DIR: 'tmp/issue27-allure-results',
23+
ALLURE_SUITE_NAME: 'prescript-regression-issue27',
24+
FORCE_COLOR: '1'
25+
}
2326
}
24-
}
25-
)
26-
} catch (error) {
27-
failed = true
28-
}
29-
assert(failed, 'Expected prescript command to fail')
27+
)
28+
} catch (error) {
29+
failed = true
30+
}
31+
assert(failed, 'Expected prescript command to fail')
32+
})
33+
}
34+
35+
action('Verify that there are JSON allure results generated', async () => {
36+
const files = glob.sync('*.json', { cwd: 'tmp/issue27-allure-results' })
37+
assert(files.length > 0, 'Expected to find JSON files')
3038
})
3139

32-
action('Verify that there is an XML allure result generated', async () => {
33-
const files = glob.sync('*.xml', { cwd: 'tmp/issue27-allure-results' })
34-
assert(files.length > 0, 'Expected to file XML files')
40+
action('Verify the test results JSON have consistent historyId', async () => {
41+
const files = glob.sync('*-result.json', {
42+
cwd: 'tmp/issue27-allure-results'
43+
})
44+
const historyIds = Array.from(
45+
new Set(
46+
files.map(
47+
f =>
48+
JSON.parse(fs.readFileSync(`tmp/issue27-allure-results/${f}`, 'utf8'))
49+
.historyId
50+
)
51+
)
52+
)
53+
expect(historyIds).toHaveLength(1)
3554
})
3655

3756
action('Generate an allure-report', async () => {

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "prescript",
3-
"version": "0.5555555.0-1",
3+
"version": "0.5555555.0-55",
44
"description": "Object-oriented acceptance test tool",
55
"main": "./lib/singletonApi.js",
66
"typings": "./lib/singletonApi.d.ts",
@@ -13,7 +13,7 @@
1313
],
1414
"dependencies": {
1515
"@types/cosmiconfig": "^5.0.3",
16-
"allure-js-commons": "^1.2.1",
16+
"allure-js-commons": "^2.0.0-beta.7",
1717
"chalk": "^2.4.1",
1818
"co": "^4.6.0",
1919
"cosmiconfig": "^5.0.7",
@@ -30,8 +30,8 @@
3030
"@types/jest": "^22.2.3",
3131
"@types/ms": "^0.7.30",
3232
"@types/node": "^10.0.0",
33-
"allure-commandline": "^2.13.0",
3433
"@typescript-eslint/parser": "^2.2.0",
34+
"allure-commandline": "^2.13.0",
3535
"eslint": "^6.3.0",
3636
"eslint-config-prettier": "^6.2.0",
3737
"eslint-plugin-prettier": "^3.1.0",

src/cli.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -357,11 +357,12 @@ async function runNext(
357357
})
358358
},
359359
attach: (name, buffer, mimeType) => {
360-
const allure = singletonAllureInstance.currentInstance
361360
const buf = Buffer.from(buffer)
362-
if (allure) {
363-
allure.addAttachment(name, buf, mimeType)
364-
}
361+
singletonAllureInstance.currentReportingInterface.addAttachment(
362+
name,
363+
buf,
364+
mimeType
365+
)
365366
context.log(
366367
'Attachment added: "%s" (%s, %s bytes)',
367368
name,
@@ -425,24 +426,21 @@ async function runNext(
425426
chalk.dim('* ') + chalk.cyan(indentString(item.text, 2).substr(2))
426427
console.log(indentString(logText, indent))
427428
}
428-
const allure = singletonAllureInstance.currentInstance
429-
if (allure) {
430-
if (log.length > 0) {
431-
const logText = log
432-
.map(item => {
433-
const prefix = `[${new Date(item.timestamp).toJSON()}] `
434-
return (
435-
prefix +
436-
indentString(item.text, prefix.length).substr(prefix.length)
437-
)
438-
})
439-
.join('\n')
440-
allure.addAttachment(
441-
'Action log',
442-
Buffer.from(logText, 'utf8'),
443-
'text/plain'
444-
)
445-
}
429+
if (log.length > 0) {
430+
const logText = log
431+
.map(item => {
432+
const prefix = `[${new Date(item.timestamp).toJSON()}] `
433+
return (
434+
prefix +
435+
indentString(item.text, prefix.length).substr(prefix.length)
436+
)
437+
})
438+
.join('\n')
439+
singletonAllureInstance.currentReportingInterface.addAttachment(
440+
'Action log',
441+
Buffer.from(logText, 'utf8'),
442+
'text/plain'
443+
)
446444
}
447445
}
448446
}

src/createReporter.ts

Lines changed: 121 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
import path from 'path'
2+
import { createHash } from 'crypto'
23
import singletonAllureInstance from './singletonAllureInstance'
4+
import {
5+
AllureRuntime,
6+
IAllureConfig,
7+
ExecutableItemWrapper,
8+
AllureTest,
9+
AllureStep,
10+
Stage,
11+
Status,
12+
LabelName
13+
} from 'allure-js-commons'
14+
import { hostname } from 'os'
15+
import { AllureWriter } from 'allure-js-commons/dist/src/writers'
316

417
export default function createReporter(testModulePath, rootStepName) {
518
if (
@@ -9,6 +22,7 @@ export default function createReporter(testModulePath, rootStepName) {
922
) {
1023
return { onFinish(errors: Error[]) {}, iterationListener: {} }
1124
}
25+
1226
const suiteName = process.env.ALLURE_SUITE_NAME || 'prescript'
1327
const getDefaultCaseName = () => {
1428
const testPath = path.relative(process.cwd(), testModulePath)
@@ -17,35 +31,121 @@ export default function createReporter(testModulePath, rootStepName) {
1731
return `${testPath}${testName ? ` - ${testName}` : ''}`
1832
}
1933
const caseName = process.env.ALLURE_CASE_NAME || getDefaultCaseName()
20-
const Allure = require('allure-js-commons')
21-
const allure = new Allure()
22-
if (process.env.ALLURE_RESULTS_DIR) {
23-
allure.options.targetDir = process.env.ALLURE_RESULTS_DIR
24-
}
25-
allure.startSuite(suiteName)
26-
allure.startCase(caseName)
27-
singletonAllureInstance.currentInstance = allure
34+
const historyId = createHash('md5')
35+
.update([suiteName, caseName].join(' / '))
36+
.digest('hex')
37+
38+
const allureConfig: IAllureConfig = {
39+
resultsDir: process.env.ALLURE_RESULTS_DIR || 'allure-results'
40+
}
41+
const writer = new AllureWriter(allureConfig)
42+
const runtime = new AllureRuntime({ ...allureConfig, writer })
43+
const group = runtime.startGroup(suiteName)
44+
const test = group.startTest(caseName)
45+
const prescriptVersion = require('../package').version
46+
test.historyId = historyId
47+
test.addLabel(LabelName.THREAD, `${process.pid}`)
48+
test.addLabel(LabelName.HOST, `${hostname()}`)
49+
test.addLabel(LabelName.FRAMEWORK, `prescript@${prescriptVersion}`)
50+
let stack: IStepStack = new TestStepStack(test)
51+
singletonAllureInstance.currentReportingInterface = {
52+
addAttachment: (name, buf, mimeType) => {
53+
const sha = createHash('sha256')
54+
.update(buf)
55+
.digest('hex')
56+
const fileName = sha + path.extname(name)
57+
writer.writeAttachment(fileName, buf)
58+
stack.getExecutableItem().addAttachment(name, mimeType as any, fileName)
59+
}
60+
}
2861
return {
2962
iterationListener: {
3063
onEnter(node) {
31-
if (node.number) allure.startStep(String(node.name), Date.now())
64+
if (!node.number) {
65+
return
66+
}
67+
stack = stack.push(String(node.name))
3268
},
3369
onExit(node, error) {
34-
if (node.number) allure.endStep(error ? 'failed' : 'passed', Date.now())
70+
if (!node.number) {
71+
return
72+
}
73+
stack = stack.pop(error)
3574
}
3675
},
3776
onFinish(errors: Error[]) {
38-
if (errors.length) {
39-
const error = errors[0]
40-
if ((error as any).__prescriptPending) {
41-
allure.endCase('pending')
42-
} else {
43-
allure.endCase('failed', error)
44-
}
45-
} else {
46-
allure.endCase('passed')
47-
}
48-
allure.endSuite()
77+
stack = stack.pop(errors[0])
78+
group.endGroup()
4979
}
5080
}
5181
}
82+
83+
type Outcome = Error | undefined
84+
85+
interface IStepStack {
86+
push: (stepName: string) => IStepStack
87+
pop: (outcome: Outcome) => IStepStack
88+
getExecutableItem: () => ExecutableItemWrapper
89+
}
90+
91+
const saveOutcome = (
92+
executableItem: ExecutableItemWrapper,
93+
outcome: Outcome
94+
) => {
95+
if (!outcome) {
96+
executableItem.status = Status.PASSED
97+
executableItem.stage = Stage.FINISHED
98+
return
99+
}
100+
if ((outcome as any).__prescriptPending) {
101+
executableItem.stage = Stage.FINISHED
102+
executableItem.status = Status.SKIPPED
103+
return
104+
}
105+
executableItem.stage = Stage.FINISHED
106+
executableItem.status = Status.FAILED
107+
executableItem.detailsMessage = outcome.message || ''
108+
executableItem.detailsTrace = outcome.stack || ''
109+
}
110+
111+
class NullStepStack implements IStepStack {
112+
push(): never {
113+
throw new Error('This should not happen: Allure stack is corrupted.')
114+
}
115+
pop(): never {
116+
throw new Error('This should not happen: Allure stack is corrupted.')
117+
}
118+
getExecutableItem(): never {
119+
throw new Error('This should not happen: Allure stack is corrupted.')
120+
}
121+
}
122+
123+
class TestStepStack implements IStepStack {
124+
constructor(private test: AllureTest) {}
125+
push(stepName: string) {
126+
return new StepStepStack(this, this.test.startStep(stepName))
127+
}
128+
pop(outcome: Outcome) {
129+
saveOutcome(this.test, outcome)
130+
this.test.endTest()
131+
return new NullStepStack()
132+
}
133+
getExecutableItem() {
134+
return this.test
135+
}
136+
}
137+
138+
class StepStepStack implements IStepStack {
139+
constructor(private parent: IStepStack, private step: AllureStep) {}
140+
push(stepName: string) {
141+
return new StepStepStack(this, this.step.startStep(stepName))
142+
}
143+
pop(outcome: Outcome) {
144+
saveOutcome(this.step, outcome)
145+
this.step.endStep()
146+
return this.parent
147+
}
148+
getExecutableItem() {
149+
return this.step
150+
}
151+
}

src/singletonAllureInstance.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
class NullReportingInterface implements IReportingInterface {
2+
addAttachment = () => null
3+
}
4+
5+
export interface IReportingInterface {
6+
addAttachment: (name: string, buf: Buffer, mimeType: string) => void
7+
}
8+
19
export default {
2-
currentInstance: null as any
10+
currentReportingInterface: new NullReportingInterface() as IReportingInterface
311
}

0 commit comments

Comments
 (0)