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:
parent
88acd8cfef
commit
d14fd0b62a
117
cli/commands/generate/__test__/challenge.test.ts
Normal file
117
cli/commands/generate/__test__/challenge.test.ts
Normal 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.`
|
||||
)
|
||||
})
|
||||
})
|
@ -78,7 +78,7 @@ export class RunTestCommand extends Command {
|
||||
programmingLanguageName: this.programmingLanguage
|
||||
})
|
||||
await solution.test()
|
||||
console.log(Test.successMessage)
|
||||
console.log(Test.SUCCESS_MESSAGE)
|
||||
return 0
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
|
@ -1,13 +1,13 @@
|
||||
import execa from 'execa'
|
||||
import ora from 'ora'
|
||||
|
||||
const CONTAINER_TAG = 'programming-challenges'
|
||||
|
||||
class Docker {
|
||||
static CONTAINER_TAG = 'programming-challenges'
|
||||
|
||||
public async build (): Promise<void> {
|
||||
const loader = ora('Building the Docker image').start()
|
||||
try {
|
||||
await execa.command(`docker build --tag=${CONTAINER_TAG} ./`)
|
||||
await execa.command(`docker build --tag=${Docker.CONTAINER_TAG} ./`)
|
||||
loader.stop()
|
||||
} catch (error) {
|
||||
loader.fail()
|
||||
@ -17,7 +17,7 @@ class Docker {
|
||||
|
||||
public async run (input: string): Promise<string> {
|
||||
const subprocess = execa.command(
|
||||
`docker run --interactive --rm ${CONTAINER_TAG}`,
|
||||
`docker run --interactive --rm ${Docker.CONTAINER_TAG}`,
|
||||
{
|
||||
input
|
||||
}
|
||||
|
@ -3,17 +3,11 @@ import execa from 'execa'
|
||||
import { Challenge } from './Challenge'
|
||||
import { Solution } from './Solution'
|
||||
|
||||
const solutionsRegex = new RegExp(
|
||||
/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 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)/
|
||||
|
||||
const dockerRegex = new RegExp(
|
||||
/templates\/docker\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/Dockerfile/
|
||||
)
|
||||
const dockerRegex = /templates\/docker\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/Dockerfile/
|
||||
|
||||
const inputOutputRegex = new RegExp(
|
||||
/challenges\/[\s\S]*\/test\/(.*)\/(input.txt|output.txt)/
|
||||
)
|
||||
const inputOutputRegex = /challenges\/[\s\S]*\/test\/(.*)\/(input.txt|output.txt)/
|
||||
|
||||
export interface GitAffectedOptions {
|
||||
isContinuousIntegration: boolean
|
||||
@ -89,31 +83,17 @@ export class GitAffected implements GitAffectedOptions {
|
||||
const affectedInputOutput = files.filter((filePath) => {
|
||||
return inputOutputRegex.test(filePath)
|
||||
})
|
||||
const affectedChallenges = affectedInputOutput.map((filePath) => {
|
||||
const affectedChallengesFromInputOutput = affectedInputOutput.map((filePath) => {
|
||||
const [, challengeName] = filePath.replaceAll('\\', '/').split('/')
|
||||
return new Challenge({ name: challengeName })
|
||||
})
|
||||
const solutionsChallenges = await Solution.getManyByPaths(affectedSolutionsPaths)
|
||||
const solutionsDocker = await Solution.getManyByProgrammingLanguages(affectedLanguages)
|
||||
const solutions: Solution[] = solutionsDocker
|
||||
for (const solution of solutionsChallenges) {
|
||||
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 solutions: Solution[] = [...solutionsDocker, ...solutionsChallenges]
|
||||
for (const challenge of affectedChallengesFromInputOutput) {
|
||||
const solutionsByChallenge = await Solution.getManyByChallenge(challenge)
|
||||
solutions.push(...solutionsByChallenge)
|
||||
}
|
||||
}
|
||||
const solutionsUnique: Solution[] = []
|
||||
for (const solution of solutions) {
|
||||
const isAlreadyIncluded = solutionsUnique.some((solutionUnique) => {
|
||||
|
@ -37,7 +37,7 @@ export class Test implements TestOptions {
|
||||
public output: string
|
||||
public stdout: string
|
||||
public elapsedTimeMilliseconds: number
|
||||
static successMessage = `${chalk.bold.green('Success:')} Tests passed! 🎉`
|
||||
static SUCCESS_MESSAGE = `${chalk.bold.green('Success:')} Tests passed! 🎉`
|
||||
|
||||
constructor (options: TestOptions) {
|
||||
this.index = options.index
|
||||
@ -99,15 +99,15 @@ export class Test implements TestOptions {
|
||||
const name = `${solution.challenge.name}/${solution.programmingLanguageName}/${solution.name}`
|
||||
const testsPath = path.join(solution.challenge.path, 'test')
|
||||
const testsFolders = await fs.promises.readdir(testsPath)
|
||||
const testsNumbers = testsFolders.map((test) => Number(test)).sort((a, b) => a - b)
|
||||
const tests: Test[] = []
|
||||
console.log(`${chalk.bold('Name:')} ${name}\n`)
|
||||
for (let index = 0; index < testsFolders.length; index++) {
|
||||
const currentTestIndex = index + 1
|
||||
const loader = ora(`Test n°${currentTestIndex}`).start()
|
||||
for (const testNumber of testsNumbers) {
|
||||
const loader = ora(`Test n°${testNumber}`).start()
|
||||
try {
|
||||
const test = await Test.run({
|
||||
path: path.join(testsPath, testsFolders[index]),
|
||||
index: currentTestIndex
|
||||
path: path.join(testsPath, testNumber.toString()),
|
||||
index: testNumber
|
||||
})
|
||||
tests.push(test)
|
||||
if (test.isSuccess) {
|
||||
@ -138,7 +138,7 @@ export class Test implements TestOptions {
|
||||
await solution.test()
|
||||
console.log('\n------------------------------\n')
|
||||
}
|
||||
console.log(Test.successMessage)
|
||||
console.log(Test.SUCCESS_MESSAGE)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
2194
package-lock.json
generated
2194
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -47,20 +47,21 @@
|
||||
"validate-npm-package-name": "3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "14.1.0",
|
||||
"@commitlint/config-conventional": "14.1.0",
|
||||
"@commitlint/cli": "15.0.0",
|
||||
"@commitlint/config-conventional": "15.0.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/node": "16.11.7",
|
||||
"@types/node": "16.11.11",
|
||||
"@types/validate-npm-package-name": "3.0.3",
|
||||
"editorconfig-checker": "4.0.2",
|
||||
"jest": "27.3.1",
|
||||
"markdownlint-cli": "0.29.0",
|
||||
"get-stream": "6.0.1",
|
||||
"jest": "27.4.2",
|
||||
"markdownlint-cli": "0.30.0",
|
||||
"mock-fs": "5.1.2",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "27.0.7",
|
||||
"ts-standard": "10.0.0",
|
||||
"typescript": "4.4.4"
|
||||
"ts-standard": "11.0.0",
|
||||
"typescript": "4.5.2"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user