Skip to content
Merged
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
2 changes: 2 additions & 0 deletions ci/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
!.gitignore
23 changes: 23 additions & 0 deletions ci/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require("dotenv").config();

const EXECUTION_RESULTS = {
NONE: 0,
SUCCESS: 1,
FAILED: 2
}

const LANGUAGE_VERSIONS = {
solidity: ['0.6.12', '0.7.5', '0.8.4'],
javascript: ['10.x', '10.x/babel']
}

const GRAPH_API = process.env.GRAPH_API || "http://localhost:3040/graphql";

const RUN_URL = process.env.RUN_URL || "https://relayer-staging.chainshot.com/run/";

module.exports = {
LANGUAGE_VERSIONS,
EXECUTION_RESULTS,
GRAPH_API,
RUN_URL,
}
56 changes: 56 additions & 0 deletions ci/execute/executeGroups.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const executeStages = require("./executeStages");
const { EXECUTION_RESULTS } = require('../config');

function sendProcessMessage(msg) {
// process.send not defined outside of child processes
// this is used for the Builder CLI
if(process.send) {
process.send(msg);
}
}

function executeGroups(groups) {
const stageContainers = groups.reduce((arr, group) => {
const containers = group.stageContainers.map((x) => ({ ...x, stageContainerGroup: group }))
return arr.concat(containers);
}, []);

const promises = new Array(stageContainers.length).fill(0).map(async (_, j) => {
const stageContainer = stageContainers[j];

const { version, stages, stageContainerGroup } = stageContainer;
const { title } = stageContainerGroup;

console.log(`Running ${title} ${version}...`);
const results = await executeStages(stages);
const executedResults = results.filter(x => x !== EXECUTION_RESULTS.NONE);
if(executedResults.length === 0) {
return;
}

console.log(`${title} ${version} results:`);
executedResults.forEach((result, i) => {
const stage = stageContainer.stages[i];
if(result === EXECUTION_RESULTS.SUCCESS) {
console.log(`✔️ ${stage.title} passed!`);
sendProcessMessage({ type: "RESULT", data: {
success: true,
version: `${title} ${version}`,
stage: stage.title
}});
}
else {
console.log(`✘ ${stage.title} failed!`);
sendProcessMessage({ type: "RESULT", data: {
success: false,
version: `${title} ${version}`,
stage: stage.title
}});
}
});
});

return Promise.all(promises);
}

module.exports = executeGroups;
28 changes: 28 additions & 0 deletions ci/execute/executeStage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const axios = require('axios');
const { RUN_URL } = require('../config');

async function executeStage(stage) {
const files = stage.codeFiles
.filter(x => x.executable)
.map(({ id, initialCode, executablePath, hasProgress }) => {
if(hasProgress) {
const solution = stage.solutions.find(x => x.codeFileId === id);
return { contents: solution.code, path: executablePath }
}
return { contents: initialCode, path: executablePath }
});

const { id, languageVersion, language, testFramework, forkBlockNumber } = stage;

return axios.post(RUN_URL, {
stageId: stage.id,
files,
languageVersion,
language,
testFramework,
forkBlockNumber,
noCache: true
});
}

module.exports = executeStage;
30 changes: 30 additions & 0 deletions ci/execute/executeStages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const { LANGUAGE_VERSIONS, EXECUTION_RESULTS } = require('../config');
const executeStage = require("./executeStage");

async function timeout(seconds) {
return new Promise((resolve) => {
setTimeout(resolve, seconds);
});
}

async function executeStages(stages) {
return Promise.all(stages.map(async (stage, i) => {
const { language, languageVersion } = stage;

await timeout(i * 500);

if((LANGUAGE_VERSIONS[language] || []).indexOf(languageVersion) < 0) {
return EXECUTION_RESULTS.NONE;
}
try {
const { data: { result: { completed }} } = await executeStage(stage);
return completed ? EXECUTION_RESULTS.SUCCESS : EXECUTION_RESULTS.FAILED;
}
catch(ex) {
console.log(ex.message);
return EXECUTION_RESULTS.FAILED;
}
}));
}

module.exports = executeStages;
58 changes: 58 additions & 0 deletions ci/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions ci/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "ci",
"version": "1.0.0",
"main": "executeStage.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.21.1"
},
"devDependencies": {},
"description": ""
}
15 changes: 15 additions & 0 deletions ci/query/getStageContainerGroupIds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const axios = require('axios');
const { GRAPH_API } = require('../config');
const { stageContainerGroupIds } = require("./queries");

async function getStageContainerGroupIds() {
const r1 = await axios.post(GRAPH_API, {
query: stageContainerGroupIds,
variables: { filter: '{ "productionReady": true }' }
});
const { data: { data: { stageContainerGroups: idObjects }}} = r1;

return idObjects.map(x => x.id);
}

module.exports = getStageContainerGroupIds;
12 changes: 12 additions & 0 deletions ci/query/getStageContainerGroups.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const axios = require('axios');
const { GRAPH_API } = require('../config');
const { stageContainerGroupsByIds } = require("./queries");

async function getStageContainerGroups(ids) {
const variables = { containsId: ids };
const r2 = await axios.post(GRAPH_API, { query: stageContainerGroupsByIds, variables });
const { data: { data: { stageContainerGroups }}} = r2;
return stageContainerGroups;
}

module.exports = getStageContainerGroups;
41 changes: 41 additions & 0 deletions ci/query/queries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const stageContainerGroupIds = `
query filterStageContainerGroups($filter: String) {
stageContainerGroups(filter: $filter) {
id
}
}`;

const stageContainerGroupsByIds = `
query stageContainerGroupsByIds($containsId: [String]) {
stageContainerGroups(containsId: $containsId) {
id
title
stageContainers {
version
stages {
id
title
languageVersion
language
testFramework
forkBlockNumber
solutions {
codeFileId
code
}
codeFiles {
id
name
hasProgress
executable
executablePath
readOnly
testFixture
initialCode
}
}
}
}
}`;

module.exports = { stageContainerGroupIds, stageContainerGroupsByIds }
29 changes: 29 additions & 0 deletions ci/startServer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const childProcess = require("child_process");
const path = require("path");
const serverPath = path.join(__dirname, "..", "server");

function startServer() {
return new Promise((resolve, reject) => {
const npmChild = childProcess.exec("npm i", { cwd: serverPath });
npmChild.on("exit", () => {
const child = childProcess.fork('src/index', [], {
CONTENT_PATH: process.env.contentPath,
CONTENT_REPO_NAME: process.env.contentRepoName,
cwd: serverPath,
detached: true,
silent: false,
env: { QUERY_ONLY: true }
});
child.on('message', (msg) => {
if(msg === "started") {
resolve(child);
}
});
child.on('error', (ex) => {
console.log(ex);
});
});
});
}

module.exports = startServer;
47 changes: 47 additions & 0 deletions ci/testAll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const startServer = require('./startServer');
const getStageContainerGroups = require('./query/getStageContainerGroups');
const getStageContainerGroupIds = require('./query/getStageContainerGroupIds');
const executeGroups = require('./execute/executeGroups');

const BATCH_AMOUNT = 1;
let serverProcess;

async function testAll() {
serverProcess = await startServer();

try {
const groupIds = await getStageContainerGroupIds();

for(let i = 0; i < groupIds.length; i+=BATCH_AMOUNT) {
const groups = await getStageContainerGroups(groupIds.slice(i, i + BATCH_AMOUNT));

await executeGroups(groups);
}
}
catch(ex) {
console.log(ex);
serverProcess.kill();
process.exit(1);
}

serverProcess.kill();
}

[`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`].forEach((eventType) => {
const cleanup = cleanUpServer.bind(null, eventType);
process.on(eventType, (...args) => {
if(eventType === "uncaughtException") {
console.log(eventType, ...args);
}
cleanup(eventType === "exit");
});
});

function cleanUpServer(success) {
if(serverProcess) {
serverProcess.kill();
}
process.exit(success ? 0 : 1);
}

testAll();
2 changes: 1 addition & 1 deletion server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/src/contentStartup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const fs = require('fs-extra');
const path = require('path');
const {CONTENT_DIR, CONFIG_DIR, TEMPLATES_DIR, PROJECTS_DIR, MODEL_DB} = require('./config');

// setups the content directory
// sets up the content directory
// if this content initialization, also add readme
async function contentStartup(initialization) {
await fs.ensureDir(CONTENT_DIR);
Expand Down
Loading