@testomatio/reporter
Version:
Testomatio Reporter Client
232 lines (230 loc) • 9.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.dataStorage = void 0;
exports.stringToMD5Hash = stringToMD5Hash;
const debug_1 = __importDefault(require("debug"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importStar(require("path"));
const os_1 = __importDefault(require("os"));
const constants_js_1 = require("./constants.js");
const utils_js_1 = require("./utils/utils.js");
const crypto_1 = __importDefault(require("crypto"));
const debug = (0, debug_1.default)('@testomatio/reporter:storage');
class DataStorage {
static #instance;
context;
/**
*
* @returns {DataStorage}
*/
static getInstance() {
if (!this.#instance) {
this.#instance = new DataStorage();
}
return this.#instance;
}
setContext(context) {
this.context = context;
}
/**
* Creates data storage instance as singleton
* Stores data to global variable or to file depending on what is applicable for current test runner (adapter)
* Recommend to use composition while using this class (instead of inheritance).
* ! Also the class which will use data storage should be singleton (to avoid data loss).
*/
constructor() {
// some frameworks use global variable to store data, some use file storage
this.isFileStorage = true;
}
/**
* Puts any data to storage (file or global variable).
* If file: stores data as text, if global variable – stores as array of data.
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
* @param {*} data anything you want to store (string, object, array, etc)
* @param {*} context could be testId or any context (test name, suite name, including their IDs etc)
* suite name + test name is used by default
* @returns
*/
putData(dataType, data, context = null) {
if (!dataType || !data)
return;
context = context || this.context || utils_js_1.testRunnerHelper.getNameOfCurrentlyRunningTest();
if (!context) {
// debug(`No context provided for "${dataType}" data:`, data);
return;
}
const contextHash = stringToMD5Hash(context);
if (this.isFileStorage) {
const dataDirPath = path_1.default.join(constants_js_1.TESTOMAT_TMP_STORAGE_DIR, dataType);
utils_js_1.fileSystem.createDir(dataDirPath);
this.#putDataToFile(dataType, data, contextHash);
}
else {
this.#putDataToGlobalVar(dataType, data, contextHash);
}
}
/**
* Returns data, stored for specific test/context (or data which was stored without test id specified).
* This method will get data from global variable and/or from from file (previosly saved with put method).
*
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
* @param {string} context
* @returns {any []} array of data (any type), null (if no data found for context) or string (if data type is log)
*/
getData(dataType, context) {
// TODO: think if it could be useful
// context = context || this.context || testRunnerHelper.getNameOfCurrentlyRunningTest();
if (!context) {
debug(`Trying to get "${dataType}" data without context`);
return null;
}
const contextHash = stringToMD5Hash(context);
let testDataFromFile = [];
let testDataFromGlobalVar = [];
if (global?.testomatioDataStore) {
testDataFromGlobalVar = this.#getDataFromGlobalVar(dataType, contextHash);
if (testDataFromGlobalVar) {
if (testDataFromGlobalVar.length)
return testDataFromGlobalVar;
}
// don't return nothing if no data in global variable
}
testDataFromFile = this.#getDataFromFile(dataType, contextHash);
if (testDataFromFile.length) {
return testDataFromFile;
}
// debug(`No "${dataType}" data for context "${contextHash}" in both file and global variable`);
// in case no data found for context
return null;
}
/**
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
* @param {string} context
* @returns aray of data (any type)
*/
#getDataFromGlobalVar(dataType, context) {
try {
if (global?.testomatioDataStore[dataType]) {
const testData = global.testomatioDataStore[dataType][context];
if (testData)
debug('<=', dataType, 'global', context, testData);
return testData || [];
}
// debug(`No ${this.dataType} data for context ${context} in <global> storage`);
return [];
}
catch (e) {
// there could be no data, ignore
}
}
/**
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
* @param {*} context
* @returns array of data (any type)
*/
#getDataFromFile(dataType, context) {
const dataDirPath = path_1.default.join(constants_js_1.TESTOMAT_TMP_STORAGE_DIR, dataType);
try {
const filepath = (0, path_1.join)(dataDirPath, `${dataType}_${context}`);
if (fs_1.default.existsSync(filepath)) {
const testDataAsText = fs_1.default.readFileSync(filepath, 'utf-8');
if (testDataAsText)
debug('<=', dataType, 'file', context, testDataAsText);
const testDataArr = testDataAsText?.split(os_1.default.EOL) || [];
return testDataArr;
}
// debug(`No ${this.dataType} data for ${context} in <file> storage`);
return [];
}
catch (e) {
// there could be no data, ignore
}
return [];
}
/**
* Puts data to global variable. Unlike the file storage, stores data in array (file storage just append as string).
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
* @param {*} data
* @param {*} context
*/
#putDataToGlobalVar(dataType, data, context) {
debug('=>', dataType, 'global', context, data);
if (!global.testomatioDataStore)
global.testomatioDataStore = {};
if (!global.testomatioDataStore?.[dataType])
global.testomatioDataStore[dataType] = {};
if (!global.testomatioDataStore?.[dataType][context])
global.testomatioDataStore[dataType][context] = [];
global.testomatioDataStore[dataType][context].push(data);
}
/**
* Puts data to file. Unlike the global variable storage, stores data as string
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
* @param {*} data
* @param {string} context
* @returns
*/
#putDataToFile(dataType, data, context) {
const dataDirPath = path_1.default.join(constants_js_1.TESTOMAT_TMP_STORAGE_DIR, dataType);
if (typeof data !== 'string')
data = JSON.stringify(data);
const filename = `${dataType}_${context}`;
const filepath = (0, path_1.join)(dataDirPath, filename);
if (!fs_1.default.existsSync(dataDirPath))
utils_js_1.fileSystem.createDir(dataDirPath);
debug('=>', dataType, 'file', context, data);
// append new line if file already exists (in this case its definitely includes some data)
if (fs_1.default.existsSync(filepath)) {
fs_1.default.appendFileSync(filepath, os_1.default.EOL + data, 'utf-8');
}
else {
fs_1.default.writeFileSync(filepath, data, 'utf-8');
}
}
}
function stringToMD5Hash(str) {
const md5 = crypto_1.default.createHash('md5');
md5.update(str);
const hash = md5.digest('hex');
return `${process.env.runId || 'run'}_${hash}`;
}
exports.dataStorage = DataStorage.getInstance();
// TODO: consider using fs promises instead of writeSync/appendFileSync to
// prevent blocking and improve performance (probably queue usage will be required)
module.exports.stringToMD5Hash = stringToMD5Hash;