Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT DELETE] Tests implementation using Gherkin #6

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Materials Design in Javascript (made.js)",
"scripts": {
"postinstall": "mkdir -p lib; babel src --out-dir lib",
"test": "mocha $NODE_DEBUG_OPTION --recursive --bail --require @babel/register/lib tests"
"test": "cucumber-js --require-module @babel/register --format node_modules/cucumber-pretty --require tests/cucumber/support tests/cucumber/features"
},
"repository": {
"type": "git",
Expand All @@ -17,17 +17,20 @@
},
"homepage": "https://github.com/Exabyte-io/made-js",
"devDependencies": {
"mocha": "^2.5.3",
"chai": "^3.5.0",
"fs-extra": "^4.0.0"
"cucumber": "^5.1.0",
"cucumber-pretty": "^1.5.0",
"fs-extra": "^4.0.0",
"mocha": "^2.5.3",
"random-seed": "^0.3.0"
},
"dependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/register": "7.0.0",
"@babel/plugin-proposal-class-properties": "7.0.0",
"@babel/polyfill": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/plugin-proposal-class-properties": "7.0.0",
"@babel/register": "7.0.0",
"array-almost-equal": "^1.0.0",
"crypto-js": "^3.1.9-1",
"lodash": "^4.17.4",
Expand Down
8 changes: 8 additions & 0 deletions tests/cucumber/features/formula.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Feature: formula is extracted properly from Basis class.

Scenario:
When Basis is created with the following data:
| basis | cacheKey |
| $BASIS{Si 0 0 0, Ge 0.25 0.25 0.25} | basis |
Then Basis stored under "basis" key returns "SiGe" as formula

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import assert from "assert";
import {Then} from "cucumber";

Then(/^Basis stored under "([^"]*)" key returns "([^"]*)" as formula$/, function (cacheKey, formula) {
assert.equal(this["cacheKey"].formula, formula);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {When} from "cucumber";

import {parseTable} from "../../table";
import {Basis} from "../../../../../src/basis/basis";

When(/^Basis is created with the following data:$/, function (table) {
const config = parseTable(table, this)[0];
this["cacheKey"] = new Basis(config.basis);
});
43 changes: 43 additions & 0 deletions tests/cucumber/support/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import debug from "debug";

const LEVELS = {
debug: 3,
warn: 2,
info: 1,
error: 0
};

const DEBUG_LEVEL = process.env.DEBUG_LEVEL || LEVELS.warn;

/**
* @summary Returns a logger with specified name.
* @param name {String} E.g. "exachimp:widget"
*/
export function getLogger(name) {
const log = debug(name);

return {
debug(message) {
if (DEBUG_LEVEL >= LEVELS.debug) {
log(message);
}
},
warn(message) {
if (DEBUG_LEVEL >= LEVELS.warn) {
log(message);
}
},
info(message) {
if (DEBUG_LEVEL >= LEVELS.info) {
log(message);
}
},
error(message) {
if (DEBUG_LEVEL >= LEVELS.error) {
log(message);
}
}
}
}

export const logger = getLogger("exachimp:default");
147 changes: 147 additions & 0 deletions tests/cucumber/support/table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import _ from "underscore";
import lodash from "lodash";
import random from "random-seed";

import {getCacheValue} from "./utils/cache";

const REGEXES = [
{
name: "DATE_REGEX",
regex: /^\$DATE\{(.*)}/,
func: (str, regex) => new Date(str.match(regex)[1])
},
{
name: "BOOLEAN_REGEX",
regex: /^\$BOOLEAN\{(.*)}/,
func: (str, regex) => JSON.parse(str.match(regex)[1])
},
{
name: "ARRAY_REGEX",
regex: /^\$ARRAY\{(.*)}/,
func: (str, regex) => str.match(regex)[1].split(",")
},
{
name: "INT_REGEX",
regex: /^\$INT\{(.*)}/,
func: (str, regex) => parseInt(str.match(regex)[1])
},
{
name: "FLOAT_REGEX",
regex: /^\$FLOAT\{(.*)}/,
func: (str, regex) => parseFloat(str.match(regex)[1])
},
{
name: "BASIS_REGEX",
regex: /^\$BASIS\{(.*)}/,
func: (str, regex) => parseBasisStr(str.match(regex)[1])
},
{
name: "EXPR_REGEX",
regex: /^\$\{(.*)}/,
func: (str, regex, context) => {
const value = str.match(regex)[1];
return value.split(',').map(evalExpression).reduceRight((mem, part) => part + mem, '');
}
},
{
name: "CACHE_REGEX",
regex: /\$CACHE\{([^\{^}]*)}/,
func: (str, regex, context) => {
const value = str.match(regex)[1];
const [contextKey, property] = value.split(':');
return parseValue(str.replace(`$CACHE{${value}}`, lodash.get(getCacheValue(context, contextKey), property)), context);
}
}
];

/**
* Checks whether passed string is integer number.
*/
function isInteger(str) {
return /^\d+$/.test(str);
}

/**
* Parses basis string in format "Si 0 0 0, Li 0.5 0.5 0.5" and returns it as an object in exabyte internal format.
*/
function parseBasisStr(str) {
const lines = str.split(/[,;]/).map(x => x.trim());
const basis = {
elements: [],
coordinates: [],
units: 'crystal'
};
for (var i = 0; i < lines.length; i++) {
var items = lines[i].split(' ');
basis.elements.push({
id: i + 1,
value: items[0]
});
basis.coordinates.push({
id: i + 1,
value: [items[1], items[2], items[3]].map(parseFloat)
});
}
return basis;
}

/**
* Evaluates expression from passed string.
* See https://www.jayway.com/2012/04/03/cucumber-data-driven-testing-tips/ for more information.
* The rules are:
* - ${} – everything inside will be parsed, strings are comma separated
* - numerical value – create random alphanumeric string
* - ! – use the string as it is
* - N – random numbers
* - d - date in
* Math.random().toString(36) is used to generate random string.
*/
function evalExpression(str) {
if (isInteger(str)) {
/**
* Seed is generated by timestamp + random string.
* Additional random string is required, because using only seed for generating string in loop
* will cause random string duplication.
*/
var seed = new Date().getTime().toString() + Math.random().toString(36);
var rand = random.create(seed);
var alphabet = "abcdefghijklmnopqrstuvwxyz";
var randomLetter = alphabet[Math.floor(rand.random() * alphabet.length)];
// !!!IMPORTANT Random letter is required in generated string because of
// issue https://exabyte.atlassian.net/browse/SOF-1719
// Generated string is used for username generation. In case of random string contains only numbers
// slug for default issue will be inappropriate (e.g., "user-1232" has "user" slug).
return randomLetter + rand.random().toString(36).substring(2, 2 + parseInt(str) - 1);
} else if (str.indexOf('!') === 0) {
// ! – use the string as it is
return str.substring(1);
} else if (str.indexOf('N') === 0) {
// random numbers
var result = '', max = 9, min = 0;
var count = parseInt(str.substring(1));
var i = 0;
for (; i < count; i++) {
result += Math.floor(Math.random() * (max - min + 1)) + min;
}
return result;
}
}

/**
* Parses passed string and returns evaluated value.
*/
export function parseValue(str, context, key) {
if (!_.isString(str)) throw new Error('Argument should be string');
const config = REGEXES.find(config => str.match(config.regex));
return config ? config.func(str, config.regex, context) : str;
}

/**
* @summary Parses values from table rows. Each column's value for each row are parsed by parseValue.
* @param table {Object} Table passed from Cucumber step definition.
* @param context {Any} Context for extracting cached values.
* @return {Object}
*/
export function parseTable(table, context) {
return table.hashes().map(hash => _.mapObject(hash, (value, key) => parseValue(value, context, key)));
}
19 changes: 19 additions & 0 deletions tests/cucumber/support/utils/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {logger} from "../logger";

export function getCacheValue(context, key) {
return context[key]
}

export function setCacheValue(context, key, value) {
context[key] = value;
}

/**
* @summary Stores a given entity config in the context.
* when useCollectionItem is passed collection item is stored instead of config.
*/
export function cacheEntityData(config, context, entityId, entityTAO) {
if (!config.cacheKey || !context) return;
logger.debug(`Entity with name "${config.name || config.username}" is stored under cacheKey "${config.cacheKey}"`);
setCacheValue(context, config.cacheKey, config.useCollectionItem ? entityTAO.findById(entityId) : config);
}