1
1
mirror of https://github.com/theoludwig/programming-challenges.git synced 2024-12-08 00:45:29 +01:00

test(cli): add commands/generate/challenge

This commit is contained in:
Divlo 2021-11-30 21:42:43 +01:00
parent 88acd8cfef
commit d14fd0b62a
No known key found for this signature in database
GPG Key ID: 6F24DA54DA3967CF
7 changed files with 1243 additions and 1145 deletions

View File

@ -0,0 +1,117 @@
import { PassThrough } from 'node:stream'
import path from 'node:path'
import fs from 'node:fs'
import chalk from 'chalk'
import getStream from 'get-stream'
import fsMock from 'mock-fs'
import date from 'date-and-time'
import { cli } from '../../../cli'
import { isExistingPath } from '../../../utils/isExistingPath'
const input = ['generate', 'challenge']
const githubUser = 'Divlo'
const challengeName = 'aaaa-test-jest'
const inputChallengeName = `--challenge=${challengeName}`
const inputGitHubUser = `--github-user=${githubUser}`
describe('programming-challenges generate challenge', () => {
beforeEach(() => {
fsMock(
{
[process.cwd()]: fsMock.load(process.cwd(), { recursive: true })
},
{ createCwd: false }
)
})
afterEach(() => {
fsMock.restore()
jest.clearAllMocks()
})
it('succeeds and generate the new challenge', async () => {
console.log = jest.fn()
const dateString = date.format(new Date(), 'D MMMM Y', true)
const stream = new PassThrough()
const exitCode = await cli.run(
[...input, inputChallengeName, inputGitHubUser],
{
stdin: process.stdin,
stdout: stream,
stderr: stream
}
)
stream.end()
expect(exitCode).toEqual(0)
const challengePath = path.join(process.cwd(), 'challenges', challengeName)
const readmePath = path.join(challengePath, 'README.md')
const readmeContent = await fs.promises.readFile(readmePath, { encoding: 'utf-8' })
const successMessage = `${chalk.bold.green('Success:')} created the new challenge at ${challengePath}.`
expect(console.log).toHaveBeenCalledWith(successMessage)
expect(await isExistingPath(challengePath)).toBeTruthy()
expect(readmeContent).toMatch(`# ${challengeName}
Created by [@${githubUser}](https://github.com/${githubUser}) on ${dateString}.
## Instructions
Description of the challenge...
## Examples
See the \`test\` folder for examples of input/output.
`)
})
it('fails without options', async () => {
const stream = new PassThrough()
const promise = getStream(stream)
const exitCode = await cli.run(input, {
stdin: process.stdin,
stdout: stream,
stderr: stream
})
stream.end()
expect(exitCode).toEqual(1)
const output = await promise
expect(output).toContain('Unknown Syntax Error')
})
it('fails with already existing challenge', async () => {
console.error = jest.fn()
const stream = new PassThrough()
const exitCode = await cli.run(
[...input, '--challenge=hello-world', inputGitHubUser],
{
stdin: process.stdin,
stdout: stream,
stderr: stream
}
)
expect(console.error).toHaveBeenCalledWith(
`${chalk.bold.red('Error:')} The challenge already exists: hello-world.`
)
stream.end()
expect(exitCode).toEqual(1)
})
it('fails with invalid challenge name', async () => {
console.error = jest.fn()
const stream = new PassThrough()
const exitCode = await cli.run(
[...input, '--challenge=hEllO-world', inputGitHubUser],
{
stdin: process.stdin,
stdout: stream,
stderr: stream
}
)
stream.end()
expect(exitCode).toEqual(1)
expect(console.error).toHaveBeenCalledWith(
`${chalk.bold.red('Error:')} Invalid challenge name.`
)
})
})

View File

@ -78,7 +78,7 @@ export class RunTestCommand extends Command {
programmingLanguageName: this.programmingLanguage programmingLanguageName: this.programmingLanguage
}) })
await solution.test() await solution.test()
console.log(Test.successMessage) console.log(Test.SUCCESS_MESSAGE)
return 0 return 0
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {

View File

@ -1,13 +1,13 @@
import execa from 'execa' import execa from 'execa'
import ora from 'ora' import ora from 'ora'
const CONTAINER_TAG = 'programming-challenges'
class Docker { class Docker {
static CONTAINER_TAG = 'programming-challenges'
public async build (): Promise<void> { public async build (): Promise<void> {
const loader = ora('Building the Docker image').start() const loader = ora('Building the Docker image').start()
try { try {
await execa.command(`docker build --tag=${CONTAINER_TAG} ./`) await execa.command(`docker build --tag=${Docker.CONTAINER_TAG} ./`)
loader.stop() loader.stop()
} catch (error) { } catch (error) {
loader.fail() loader.fail()
@ -17,7 +17,7 @@ class Docker {
public async run (input: string): Promise<string> { public async run (input: string): Promise<string> {
const subprocess = execa.command( const subprocess = execa.command(
`docker run --interactive --rm ${CONTAINER_TAG}`, `docker run --interactive --rm ${Docker.CONTAINER_TAG}`,
{ {
input input
} }

View File

@ -3,17 +3,11 @@ import execa from 'execa'
import { Challenge } from './Challenge' import { Challenge } from './Challenge'
import { Solution } from './Solution' import { Solution } from './Solution'
const solutionsRegex = new RegExp( const solutionsRegex = /challenges\/[\s\S]*\/solutions\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/[\s\S]*\/(.*).(c|cpp|cs|dart|java|js|py|rs|ts)/
/challenges\/[\s\S]*\/solutions\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/[\s\S]*\/(.*).(c|cpp|cs|dart|java|js|py|rs|ts)/
)
const dockerRegex = new RegExp( const dockerRegex = /templates\/docker\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/Dockerfile/
/templates\/docker\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/Dockerfile/
)
const inputOutputRegex = new RegExp( const inputOutputRegex = /challenges\/[\s\S]*\/test\/(.*)\/(input.txt|output.txt)/
/challenges\/[\s\S]*\/test\/(.*)\/(input.txt|output.txt)/
)
export interface GitAffectedOptions { export interface GitAffectedOptions {
isContinuousIntegration: boolean isContinuousIntegration: boolean
@ -89,31 +83,17 @@ export class GitAffected implements GitAffectedOptions {
const affectedInputOutput = files.filter((filePath) => { const affectedInputOutput = files.filter((filePath) => {
return inputOutputRegex.test(filePath) return inputOutputRegex.test(filePath)
}) })
const affectedChallenges = affectedInputOutput.map((filePath) => { const affectedChallengesFromInputOutput = affectedInputOutput.map((filePath) => {
const [, challengeName] = filePath.replaceAll('\\', '/').split('/') const [, challengeName] = filePath.replaceAll('\\', '/').split('/')
return new Challenge({ name: challengeName }) return new Challenge({ name: challengeName })
}) })
const solutionsChallenges = await Solution.getManyByPaths(affectedSolutionsPaths) const solutionsChallenges = await Solution.getManyByPaths(affectedSolutionsPaths)
const solutionsDocker = await Solution.getManyByProgrammingLanguages(affectedLanguages) const solutionsDocker = await Solution.getManyByProgrammingLanguages(affectedLanguages)
const solutions: Solution[] = solutionsDocker const solutions: Solution[] = [...solutionsDocker, ...solutionsChallenges]
for (const solution of solutionsChallenges) { for (const challenge of affectedChallengesFromInputOutput) {
if (!affectedLanguages.includes(solution.programmingLanguageName)) {
solutions.push(solution)
}
}
for (const challenge of affectedChallenges) {
let isSolutionIncluded = false
for (const solution of solutions) {
if (solution.challenge.name === challenge.name) {
isSolutionIncluded = true
break
}
}
if (!isSolutionIncluded) {
const solutionsByChallenge = await Solution.getManyByChallenge(challenge) const solutionsByChallenge = await Solution.getManyByChallenge(challenge)
solutions.push(...solutionsByChallenge) solutions.push(...solutionsByChallenge)
} }
}
const solutionsUnique: Solution[] = [] const solutionsUnique: Solution[] = []
for (const solution of solutions) { for (const solution of solutions) {
const isAlreadyIncluded = solutionsUnique.some((solutionUnique) => { const isAlreadyIncluded = solutionsUnique.some((solutionUnique) => {

View File

@ -37,7 +37,7 @@ export class Test implements TestOptions {
public output: string public output: string
public stdout: string public stdout: string
public elapsedTimeMilliseconds: number public elapsedTimeMilliseconds: number
static successMessage = `${chalk.bold.green('Success:')} Tests passed! 🎉` static SUCCESS_MESSAGE = `${chalk.bold.green('Success:')} Tests passed! 🎉`
constructor (options: TestOptions) { constructor (options: TestOptions) {
this.index = options.index this.index = options.index
@ -99,15 +99,15 @@ export class Test implements TestOptions {
const name = `${solution.challenge.name}/${solution.programmingLanguageName}/${solution.name}` const name = `${solution.challenge.name}/${solution.programmingLanguageName}/${solution.name}`
const testsPath = path.join(solution.challenge.path, 'test') const testsPath = path.join(solution.challenge.path, 'test')
const testsFolders = await fs.promises.readdir(testsPath) const testsFolders = await fs.promises.readdir(testsPath)
const testsNumbers = testsFolders.map((test) => Number(test)).sort((a, b) => a - b)
const tests: Test[] = [] const tests: Test[] = []
console.log(`${chalk.bold('Name:')} ${name}\n`) console.log(`${chalk.bold('Name:')} ${name}\n`)
for (let index = 0; index < testsFolders.length; index++) { for (const testNumber of testsNumbers) {
const currentTestIndex = index + 1 const loader = ora(`Test n°${testNumber}`).start()
const loader = ora(`Test n°${currentTestIndex}`).start()
try { try {
const test = await Test.run({ const test = await Test.run({
path: path.join(testsPath, testsFolders[index]), path: path.join(testsPath, testNumber.toString()),
index: currentTestIndex index: testNumber
}) })
tests.push(test) tests.push(test)
if (test.isSuccess) { if (test.isSuccess) {
@ -138,7 +138,7 @@ export class Test implements TestOptions {
await solution.test() await solution.test()
console.log('\n------------------------------\n') console.log('\n------------------------------\n')
} }
console.log(Test.successMessage) console.log(Test.SUCCESS_MESSAGE)
return 0 return 0
} }

2194
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -47,20 +47,21 @@
"validate-npm-package-name": "3.0.0" "validate-npm-package-name": "3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "14.1.0", "@commitlint/cli": "15.0.0",
"@commitlint/config-conventional": "14.1.0", "@commitlint/config-conventional": "15.0.0",
"@types/date-and-time": "0.13.0", "@types/date-and-time": "0.13.0",
"@types/jest": "27.0.2", "@types/jest": "27.0.3",
"@types/mock-fs": "4.13.1", "@types/mock-fs": "4.13.1",
"@types/node": "16.11.7", "@types/node": "16.11.11",
"@types/validate-npm-package-name": "3.0.3", "@types/validate-npm-package-name": "3.0.3",
"editorconfig-checker": "4.0.2", "editorconfig-checker": "4.0.2",
"jest": "27.3.1", "get-stream": "6.0.1",
"markdownlint-cli": "0.29.0", "jest": "27.4.2",
"markdownlint-cli": "0.30.0",
"mock-fs": "5.1.2", "mock-fs": "5.1.2",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"ts-jest": "27.0.7", "ts-jest": "27.0.7",
"ts-standard": "10.0.0", "ts-standard": "11.0.0",
"typescript": "4.4.4" "typescript": "4.5.2"
} }
} }