diff --git a/dev/src/ExercismDev/ExercismExerciseGenerator.class.st b/dev/src/ExercismDev/ExercismExerciseGenerator.class.st index ab8e14f3..70f08dd1 100644 --- a/dev/src/ExercismDev/ExercismExerciseGenerator.class.st +++ b/dev/src/ExercismDev/ExercismExerciseGenerator.class.st @@ -1,9 +1,21 @@ " -I am the source code generator for creating exercism compatible source files that can be checked into the exercism/pharo project for students to download. +I am the source code generator for creating test class with test cases that are generated out of problem specifications. Generated tests are then used for exercism compatible source files that can be checked into the exercism/pharo project for students to download. You need to have checked out the exercism problem-specifications to point the generator to, to get the test case definitions. -To try: self generate +## Generate new exercises +To prompt user with choosing directory with problem specifications and generate: +`ExercismExerciseGenerator generate` + +To generate individual exercise from problem specification: +`ExercismExerciseGenerator new generateExerciseFrom: 'path-to-problem-specifications/exercises/exercise-slug' asFileReference` + +## Regenerating existing exercises +Use only when need to update tests from problem specifications, existing class will be used in existing exercise package, only test methods will be overwritten (UUID and other methods will remain same). +`ExercismExerciseGenerator new regenerateExistingExercisesFrom: 'path-to-problem-specifications/exercises' asFileReference` + +When working just on specific exercise - its update: +`ExercismExerciseGenerator new regenerateExerciseFrom: 'path-to-problem-specifications/exercises/exercise-slug' asFileReference` " Class { #name : #ExercismExerciseGenerator, @@ -11,7 +23,8 @@ Class { #instVars : [ 'numberGenerated', 'exerciseDirReference', - 'testJson' + 'testJson', + 'regenerateExisting' ], #classVars : [ 'DefaultPath' @@ -46,7 +59,7 @@ ExercismExerciseGenerator class >> generate [ chooseDirectory: 'Select the /exercises location in a full Exercism/problem-specifications git project' path: self defaultPath. - path ifNotNil: [ self new generateFrom: (self defaultPath: path) ] + path ifNotNil: [ self new generateFrom: path ] ] { #category : #examples } @@ -72,6 +85,12 @@ ExercismExerciseGenerator class >> writeLegacyPackageBaselineNames [ show: '''Exercise@' , n , '''' ] ] +{ #category : #internal } +ExercismExerciseGenerator >> canOverwriteExisting [ + + ^ self regenerateExisting or: [self exerciseTestAlreadyExists not] +] + { #category : #internal } ExercismExerciseGenerator >> compile: src for: aClass selector: aSelector protocol: aName [ @@ -111,6 +130,14 @@ ExercismExerciseGenerator >> exerciseDirReference: anObject [ exerciseDirReference := anObject ] +{ #category : #generation } +ExercismExerciseGenerator >> exerciseDirectoriesDo: exerciseGenerateBlock [ + + self class defaultPath entries + select: #isDirectory + thenDo: [:dirEntry | exerciseGenerateBlock value: dirEntry ] +] + { #category : #internal } ExercismExerciseGenerator >> exerciseIsDeprecated [ @@ -123,6 +150,12 @@ ExercismExerciseGenerator >> exerciseTestAlreadyExists [ ^ Smalltalk hasClassNamed: self testClassName ] +{ #category : #internal } +ExercismExerciseGenerator >> exerciseVariableName [ + + ^ self testNameCamelCased asValidSelector asString +] + { #category : #generation } ExercismExerciseGenerator >> generateExerciseCommentFor: testClass [ | comment | @@ -146,10 +179,14 @@ ExercismExerciseGenerator >> generateExerciseFrom: aFileSystemReference [ "this is needed, from exercise directory all artefacts will be obtained" self exerciseDirReference: aFileSystemReference. + self hasValidTestDecriptions ifFalse: [^ self log: 'does not contain any test descriptions (skipping).' for: aFileSystemReference basename ]. + self exerciseIsDeprecated ifTrue: [ ^self log: 'is deprecated (skipping)' for: self testClassName ]. - self exerciseTestAlreadyExists ifTrue: [ ^self log: 'already exists (skipping)' for: self testClassName ]. + + self canOverwriteExisting + ifFalse: [ ^self log: 'exercise test class already exists (skipping).' for: self testClassName ]. testClass := self generateTestClass. self generateSetupFor: testClass. @@ -157,24 +194,24 @@ ExercismExerciseGenerator >> generateExerciseFrom: aFileSystemReference [ self generateMetaDataFor: testClass. self numberGenerated: self numberGenerated + 1. - self log: 'successfully created' for: self testClassName + self log: 'successfully created.' for: self testClassName - - - ] { #category : #generation } ExercismExerciseGenerator >> generateFrom: filePathReference [ + "set default path from parameter" + self class defaultPath: filePathReference. + "create WIP package for exercises, if missing" self ensureCreateExerciseWIPPackage. self traceCr: 'Generating new TestCases from specification: ', filePathReference printString. self numberGenerated: 0. - filePathReference entries - do: [ :entry | self generateExerciseFrom: entry reference ]. + + self exerciseDirectoriesDo: [:dirEntry | self generateExerciseFrom: dirEntry reference ]. self traceCr: ('Generation complete. Created {1} Tests!' @@ -187,6 +224,9 @@ ExercismExerciseGenerator >> generateMetaDataFor: testClass [ "write commment with exercise info to class" self generateExerciseCommentFor: testClass. + "Create UUID and version, only if creating new exercise (not regenerating existing)" + self regenerateExisting ifTrue: [ ^ self ]. + "compile method with uuid" self generateUUIDMethodFor: testClass. @@ -202,7 +242,7 @@ ExercismExerciseGenerator >> generateSetupFor: testClass [ outStream << 'setUp'; cr; tab; << 'super setUp.'; cr; - tab; << self testVariableName; << ' := '; << self testNameCamelCased; << ' new'. + tab; << self exerciseVariableName; << ' := '; << self testNameCamelCased; << ' new'. ]. self compile: src for: testClass selector: #setUp protocol: 'running' @@ -213,10 +253,10 @@ ExercismExerciseGenerator >> generateSetupFor: testClass [ ExercismExerciseGenerator >> generateTestClass [ ^ ExercismTest << self testClassName asSymbol - slots: {self testVariableName asSymbol}; + slots: {self exerciseVariableName asSymbol}; sharedVariables: {}; tag: self testNameCamelCased; - package: 'ExercismWIP'; + package: self packageNameForTestClass; install ] @@ -235,7 +275,7 @@ ExercismExerciseGenerator >> generateTestMethodsFor: testClass [ testMethodGenerator testClass: testClass; testCaseJson: testCaseJson; - testVariable: self testVariableName; + testVariable: self exerciseVariableName; testPrefix: ''; generateTests. ] @@ -270,12 +310,23 @@ ExercismExerciseGenerator >> generateVersionMethodFor: testClass [ ] +{ #category : #generation } +ExercismExerciseGenerator >> hasValidTestDecriptions [ + + "answer true, if directory with problem description contains valid test description - must contain file canonical-data.json" + + ^ self exerciseDirReference fileNames includes: 'canonical-data.json' +] + { #category : #initialization } ExercismExerciseGenerator >> initialize [ super initialize. "reset number of generated test classes" self numberGenerated: 0. + + "by default don not regenerate existing exercises, new exercise generation is default" + self regenerateExisting: false. ] { #category : #internal } @@ -301,6 +352,55 @@ ExercismExerciseGenerator >> numberGenerated: anObject [ numberGenerated := anObject ] +{ #category : #internal } +ExercismExerciseGenerator >> packageNameForTestClass [ + + "if regenerating existing class, use exercise package like 'Exercise@SlugName', otherwise just WIP package" + self regenerateExisting ifTrue: [ + ^ 'Exercise@{1}' format: {self testNameCamelCased} + ]. + ^ self defaultPackageName +] + +{ #category : #generation } +ExercismExerciseGenerator >> regenerateExerciseFrom: aFileSystemReference [ + + self regenerateExisting: true. + self generateExerciseFrom: aFileSystemReference +] + +{ #category : #accessing } +ExercismExerciseGenerator >> regenerateExisting [ + + ^ regenerateExisting +] + +{ #category : #accessing } +ExercismExerciseGenerator >> regenerateExisting: aBool [ + + regenerateExisting := aBool +] + +{ #category : #generation } +ExercismExerciseGenerator >> regenerateExistingExercisesFrom: filePathReference [ + + "this will regenerate already existing exercises from problem specifications" + self traceCr: 'Regenerating existing TestCases from specification: ', filePathReference printString. + + self numberGenerated: 0. + ExercismExercise allExercises do: [:existingExercise | + filePathReference entries + do: [ :entry | + entry name = existingExercise name ifTrue: [ + self regenerateExerciseFrom: entry reference + ] + ] + ]. + self + traceCr: ('Existing exercises sucessfully regenerated with: {1} Tests!' + format: {self numberGenerated}) +] + { #category : #internal } ExercismExerciseGenerator >> testClassName [ @@ -334,12 +434,6 @@ ExercismExerciseGenerator >> testNameCamelCased [ ] -{ #category : #internal } -ExercismExerciseGenerator >> testVariableName [ - - ^ (self testNameCamelCased, 'Calculator') asValidSelector asString -] - { #category : #internal } ExercismExerciseGenerator >> updateCategorisation [ "utility script to fix categorisations" diff --git a/dev/src/ExercismDev/ExercismGenerator.class.st b/dev/src/ExercismDev/ExercismGenerator.class.st index 060fe0a1..383e249d 100644 --- a/dev/src/ExercismDev/ExercismGenerator.class.st +++ b/dev/src/ExercismDev/ExercismGenerator.class.st @@ -55,7 +55,8 @@ Class { 'codeExporter', 'exercismExercise', 'exercisesPath', - 'osSubProcess' + 'osSubProcess', + 'updateConfig' ], #classVars : [ 'DefaultPath' @@ -65,7 +66,10 @@ Class { { #category : #helper } ExercismGenerator class >> defaultPath [ - ^ DefaultPath ifNil: [ self defaultPath: FileLocator home pathString] + + ^ DefaultPath ifNil: [ + self defaultPath: (self exercismRepositoryPath ifNil: [FileLocator home pathString]) + ] ] { #category : #helper } @@ -73,6 +77,14 @@ ExercismGenerator class >> defaultPath: pathString [ ^ DefaultPath := pathString ] +{ #category : #helper } +ExercismGenerator class >> exercismRepositoryPath [ + + |repository| + repository := (IceRepository repositoryNamed: 'pharo-smalltalk') ifNil: [ self traceCr: 'Could not determine location of local Pharo Exercism repository.'. ^ nil ]. + ^ repository location pathString +] + { #category : #generation } ExercismGenerator class >> generate [ "This is the entry point for generating exercism compatible source files that can be checked into @@ -106,12 +118,31 @@ ExercismGenerator >> basePathReference [ ^ self exercisesPath parent ] +{ #category : #helper } +ExercismGenerator >> checkOrUpdateConfiglet [ + + (self configletRootReference / 'configlet') isFile + ifTrue: [ ^ self ]. + self traceCr: 'Could not find configlet. Fetching configlet...'. + self fetchConfiglet +] + { #category : #accessing } ExercismGenerator >> codeExporter: anObject [ codeExporter := anObject ] +{ #category : #accessing } +ExercismGenerator >> configletRootReference [ + + |configletPath| + "use default bin directory where configlet is locatated (out from git repository). If not present, use parent of exercises dir." + configletPath := self class exercismRepositoryPath. + configletPath ifNil: [ ^ self basePathReference / 'bin' ]. + ^ configletPath asFileReference / 'bin' +] + { #category : #helper } ExercismGenerator >> createTagSnapshotFor: packageOrTag [ | parentSnapshot | @@ -125,6 +156,13 @@ ExercismGenerator >> createTagSnapshotFor: packageOrTag [ [ :mc | mc className isNil or: [ mc actualClass category endsWith: packageOrTag name ] ]) ] +{ #category : #helper } +ExercismGenerator >> exerciseDirReferenceFrom: exerciseName [ + "returns dir reference to exercise dir - e.g. /exercises/two-fer/" + + ^ self exercisesPath asFileReference / exerciseName +] + { #category : #accessing } ExercismGenerator >> exercisesPath [ @@ -154,9 +192,26 @@ ExercismGenerator >> exercismExercise: anExerciseClass [ exercismExercise := anExerciseClass ] +{ #category : #helper } +ExercismGenerator >> fetchConfiglet [ + + |result | + result := self osSubProcess new + workingDirectory: self configletRootReference fullName; + command: 'fetch-configlet'; + arguments: #(); + redirectStdout; + runAndWait. + + result isSuccess ifFalse: [ + self error: 'Cannot fetch configlet - ' , result lastError printString ]. + self traceCr: 'Configlet fetching - done!' +] + { #category : #generation } ExercismGenerator >> generate [ - + "this will regenerate all active exercises" + "generate source files for active exercises" self generateSourceFilesForActiveExercises. @@ -164,7 +219,7 @@ ExercismGenerator >> generate [ ExercismConfigGenerator generateTo: self basePathReference. "run configlet generation" - self runConfigletCommand. + self runConfigletCommandsForActiveExercices. ] { #category : #helper } @@ -192,6 +247,32 @@ ExercismGenerator >> generateCustomDataFor: anExercismExercise to: destinationDi lf ]] ] +{ #category : #generation } +ExercismGenerator >> generateForExercise: slugName [ + + | exerciseToGenerate | + exerciseToGenerate := self exercismExercise + find: slugName + ifAbsent: [ self error: ('Exercise {1} not found.' format: { slugName }) ]. + + "generate source files for given exercise" + self generateSourceFilesFor: exerciseToGenerate exercisePackage. + + "generate configuration config.json file" + ExercismConfigGenerator generateTo: self basePathReference. + + "run configlet commands for given exercise" + self runTestSpecificationsUpdateForExercise: slugName. + self runDocsAndMetadataUpdateForExercise: slugName. +] + +{ #category : #helper } +ExercismGenerator >> generateReadmeHintAndCustomDataOf: testClass to: metaDirectoryRef [ + + self generateReadmeHintFor: testClass exercise to: metaDirectoryRef. + testClass isCustom ifTrue: [ self generateCustomDataFor: testClass exercise to: metaDirectoryRef ] +] + { #category : #helper } ExercismGenerator >> generateReadmeHintFor: anExercismExercise to: destinationDirectory [ "Generate markdown hints, that exercism configlet will pickup for readme.md files @@ -208,51 +289,35 @@ ExercismGenerator >> generateReadmeHintFor: anExercismExercise to: destinationDi ] { #category : #helper } -ExercismGenerator >> generateSourceFilesFor: packageOrTag to: filePathString [ +ExercismGenerator >> generateSourceFilesFor: packageOrTag [ "Generate the Tonel source files for a package (normally a tag). Answer the exercise directory reference" - | exampleDirectoryRef exerciseDirectoryRef metaDirectoryRef solutionDirectoryRef testClass testClassFilename exerciseName testClasses | - - "Note: could create the writer on a memory stream to then pick what should be stored on disk - e.g. - mem := FileSystem memory root. - writer := ExTonelWriter on: mem." - - exerciseName := ExercismExercise exerciseNameFrom: packageOrTag. - exampleDirectoryRef := filePathString asFileReference. - exerciseDirectoryRef := exampleDirectoryRef / exerciseName. + | exerciseDirectoryRef metaDirectoryRef solutionDirectoryRef testClass exerciseName testClasses | + + exerciseName := self exercismExercise exerciseNameFrom: packageOrTag. + exerciseDirectoryRef := self exerciseDirReferenceFrom: exerciseName. metaDirectoryRef := exerciseDirectoryRef / '.meta'. solutionDirectoryRef := metaDirectoryRef / 'solution'. - exerciseDirectoryRef ensureCreateDirectory. - exerciseDirectoryRef deleteAll. + self prepareExerciseDirectory: exerciseDirectoryRef. codeExporter directoryReference: solutionDirectoryRef; writeSnapshot: (self createTagSnapshotFor: packageOrTag). "move files to root solution directory and remove unnecessary package dir" - ((solutionDirectoryRef / packageOrTag name) allChildrenMatching: '*.st') do: [:aFile | - aFile moveTo: solutionDirectoryRef - ]. - (solutionDirectoryRef / packageOrTag name) delete. - - "Remove the package file as its not needed for Exercism" - (solutionDirectoryRef / 'package.st') delete. + self moveSolutionFilesOfPackage: packageOrTag to: solutionDirectoryRef. "Move the test file down to the exerciseDirectory" - testClasses := packageOrTag classes select: [ :cls | cls superclass = ExercismTest ]. - testClasses do: [ :tc | - testClassFilename := tc name, '.class.st'. - (solutionDirectoryRef / testClassFilename) moveTo: exerciseDirectoryRef / testClassFilename ]. + testClasses := self testClassesOfPackage: packageOrTag. + self moveTestClasses: testClasses from: solutionDirectoryRef to: exerciseDirectoryRef. - testClass := testClasses detect: [ :tc | tc class includesSelector: #exercise ]. - self generateReadmeHintFor: testClass exercise to: metaDirectoryRef. + "Generate readme hint and custom metadata" + testClass := testClasses detect: [ :tc | tc class includesSelector: #exercise ]. + self generateReadmeHintAndCustomDataOf: testClass to: metaDirectoryRef. - testClass isCustom ifTrue: [ self generateCustomDataFor: testClass exercise to: metaDirectoryRef ]. + ^exerciseDirectoryRef - ^exerciseDirectoryRef - ] { #category : #generation } @@ -261,9 +326,31 @@ ExercismGenerator >> generateSourceFilesForActiveExercises [ self exercismExercise allExercises select: [ :ex | ex isActive ] thenDo: [ :ex | - self - generateSourceFilesFor: ex exercisePackage - to: self exercisesPath ] + self generateSourceFilesFor: ex exercisePackage + ] +] + +{ #category : #helper } +ExercismGenerator >> moveSolutionFilesOfPackage: packageOrTag to: solutionDirectoryRef [ + + "move files to root solution directory and remove unnecessary package dir" + ((solutionDirectoryRef / packageOrTag name) allChildrenMatching: '*.st') do: [:aFile | + aFile moveTo: solutionDirectoryRef + ]. + (solutionDirectoryRef / packageOrTag name) delete. + + "Remove the package file as its not needed for Exercism" + (solutionDirectoryRef / 'package.st') delete +] + +{ #category : #helper } +ExercismGenerator >> moveTestClasses: aClasses from: solutionDirectoryRef to: exerciseDirectoryRef [ + + aClasses do: [ :tc | + |testClassFilename| + testClassFilename := '{1}.class.st' format: {tc name}. + (solutionDirectoryRef / testClassFilename) moveTo: exerciseDirectoryRef / testClassFilename + ] ] { #category : #accessing } @@ -282,15 +369,68 @@ ExercismGenerator >> osSubProcess: anOsSubProcess [ osSubProcess := anOsSubProcess ] +{ #category : #helper } +ExercismGenerator >> prepareExerciseDirectory: exerciseDirReference [ + + exerciseDirReference ensureCreateDirectory. + exerciseDirReference deleteAll. +] + { #category : #generation } -ExercismGenerator >> runConfigletCommand [ - |result | +ExercismGenerator >> runConfigletCommandsForActiveExercices [ + + self exercismExercise allExercises + select: [ :ex | ex isActive ] + thenDo: [ :ex | + self runTestSpecificationsUpdateForExercise: ex name. + self runDocsAndMetadataUpdateForExercise: ex name. + ] +] + +{ #category : #generation } +ExercismGenerator >> runDocsAndMetadataUpdateForExercise: slugName [ + | result | + + result := self osSubProcess new + command: 'configlet'; + workingDirectory: self configletRootReference fullName; + arguments: {'sync'. '--docs'. '--filepaths'. '--metadata'. '-uy'. '-e'. slugName.} asArray; + redirectStdout; + runAndWait. + + result isSuccess ifFalse: [ + self error: ('Failed running "configlet sync --docs --filepaths --metadata -uy -e {1}" with result: {2}' format: { slugName. result lastError printString.})] +] + +{ #category : #generation } +ExercismGenerator >> runTestSpecificationsUpdateForExercise: slugName [ + | result | + result := self osSubProcess new - command: 'configlet generate'; - arguments: (Array with: self basePathReference pathString surroundedBySingleQuotes); + command: 'configlet'; + workingDirectory: self configletRootReference fullName; + arguments: {'sync'. '-u'. '--tests'. 'include'. '-e'. slugName.} asArray; redirectStdout; runAndWait. result isSuccess ifFalse: [ - self error: 'failure running "configlet generate" - ' , result lastError printString ] + self error: ('Failed running "configlet sync -u --tests include -e {1}" with result: {2}' format: { slugName. result lastError printString.})] +] + +{ #category : #helper } +ExercismGenerator >> testClassesOfPackage: packageOrTag [ + + ^ packageOrTag classes select: [ :cls | cls superclass = ExercismTest ] +] + +{ #category : #accessing } +ExercismGenerator >> updateConfig [ + + ^ updateConfig +] + +{ #category : #accessing } +ExercismGenerator >> updateConfig: aBool [ + + updateConfig := aBool ] diff --git a/dev/src/ExercismTests/DiskStore.extension.st b/dev/src/ExercismTests/DiskStore.extension.st new file mode 100644 index 00000000..e13cba90 --- /dev/null +++ b/dev/src/ExercismTests/DiskStore.extension.st @@ -0,0 +1,10 @@ +Extension { #name : #DiskStore } + +{ #category : #'*ExercismTests' } +DiskStore class >> currentFileSystem: fileSystem during: aBlock [ + | backupFileSystem | + backupFileSystem := self currentFileSystem. + [ CurrentFS := fileSystem. + aBlock value ] + ensure: [ CurrentFS:= backupFileSystem ] +] diff --git a/dev/src/ExercismTests/ExercismExerciseGeneratorTest.class.st b/dev/src/ExercismTests/ExercismExerciseGeneratorTest.class.st index 0fa1c27d..9b2eefb1 100644 --- a/dev/src/ExercismTests/ExercismExerciseGeneratorTest.class.st +++ b/dev/src/ExercismTests/ExercismExerciseGeneratorTest.class.st @@ -106,6 +106,16 @@ ExercismExerciseGeneratorTest >> testAsValidKeywordOnString [ self assert: 'is <= 5' asValidKeyword equals: 'isLessThanOrEqualTo5' ] +{ #category : #tests } +ExercismExerciseGeneratorTest >> testExerciseVariableName [ + |generator| + "set directory reference with canonical description with tests of given exercise" + generator := ExercismExerciseGenerator new exerciseDirReference: + self createMockExerciseDirectory. + + self assert: generator exerciseVariableName equals: 'mockExercise'. +] + { #category : #tests } ExercismExerciseGeneratorTest >> testGenerateExerciseFrom [ |aPackage aClass testSelectors classSelectors| @@ -143,3 +153,33 @@ ExercismExerciseGeneratorTest >> testGenerateExerciseFrom [ ] + +{ #category : #tests } +ExercismExerciseGeneratorTest >> testHasValidTestDecriptions [ + |generator| + "set directory reference with canonical description with tests of given exercise" + generator := ExercismExerciseGenerator new exerciseDirReference: + self createMockExerciseDirectory. + + self assert: generator hasValidTestDecriptions. + + "now remove test descriptions" + (generator exerciseDirReference / 'canonical-data.json') ensureDelete. + self deny: generator hasValidTestDecriptions. +] + +{ #category : #tests } +ExercismExerciseGeneratorTest >> testPackageNameForTestClass [ + |generator| + "set directory reference with canonical description with tests of given exercise" + generator := ExercismExerciseGenerator new exerciseDirReference: + self createMockExerciseDirectory. + + "for new exercises, package should be set to work-in-progress" + generator regenerateExisting: false. + self assert: generator packageNameForTestClass equals: 'ExercismWIP'. + + "for existing exercises with solution already, it should be exercise package" + generator regenerateExisting: true. + self assert: generator packageNameForTestClass equals: 'Exercise@MockExercise'. +] diff --git a/dev/src/ExercismTests/ExercismGeneratorTest.class.st b/dev/src/ExercismTests/ExercismGeneratorTest.class.st index 0e6d1ab7..e2af66a9 100644 --- a/dev/src/ExercismTests/ExercismGeneratorTest.class.st +++ b/dev/src/ExercismTests/ExercismGeneratorTest.class.st @@ -168,6 +168,35 @@ ExercismGeneratorTest >> tearDown [ super tearDown ] +{ #category : #tests } +ExercismGeneratorTest >> testCheckOrUpdateConfiglet [ + |memFileSystem| + instance osSubProcess: FailedTestOSProcess. + + "memory file system should be used instead of system for testing purposes" + memFileSystem := instance exercisesPath fileSystem. + DiskStore + currentFileSystem: memFileSystem + during: [ + "when configlet not present - should proceed to fetching and should fail" + self should: [instance checkOrUpdateConfiglet] + raise: Error + whoseDescriptionIncludes: 'Cannot fetch configlet' + description: 'Checking should proceed to fetching of configlet and should fail as exepected result of FailedTestOSProcess.'. + ]. + + DiskStore + currentFileSystem: memFileSystem + during: [ + (instance configletRootReference / 'configlet') ensureCreateFile. + "when configlet is present (fake file above) - it should not proceed to fetching" + self shouldnt: [instance checkOrUpdateConfiglet] + raise: Error + whoseDescriptionIncludes: 'Cannot fetch configlet' + description: 'Checking of configlet should pass and not proceed to fetching.'. + ] +] + { #category : #tests } ExercismGeneratorTest >> testFailedGenerateSignalsException [ @@ -178,6 +207,25 @@ ExercismGeneratorTest >> testFailedGenerateSignalsException [ description: 'Did not signal an error after succesful generation' ] +{ #category : #tests } +ExercismGeneratorTest >> testFetchConfiglet [ + + instance osSubProcess: SuccessfulTestOSProcess. + + self shouldnt: [instance fetchConfiglet] + raise: Error + whoseDescriptionIncludes: 'Cannot fetch configlet' + description: 'Fetching of configlet should success.'. + + instance osSubProcess: FailedTestOSProcess. + + self should: [instance fetchConfiglet] + raise: Error + whoseDescriptionIncludes: 'Cannot fetch configlet' + description: 'Fetching of configlet should fail.'. + +] + { #category : #tests } ExercismGeneratorTest >> testGenerate [ @@ -190,6 +238,18 @@ ExercismGeneratorTest >> testGenerate [ self assertSolution ] +{ #category : #tests } +ExercismGeneratorTest >> testGenerateForExercise [ + + instance osSubProcess: SuccessfulTestOSProcess. + + instance generateForExercise: 'two-fer'. + + self assertExerciseTests. + self assertHints. + self assertSolution +] + { #category : #tests } ExercismGeneratorTest >> testGenerateSourceFilesForActiveExercises [ diff --git a/dev/src/ExercismTests/MockTestOSProcess.class.st b/dev/src/ExercismTests/MockTestOSProcess.class.st index ee8b0789..c1f11896 100644 --- a/dev/src/ExercismTests/MockTestOSProcess.class.st +++ b/dev/src/ExercismTests/MockTestOSProcess.class.st @@ -39,3 +39,8 @@ MockTestOSProcess >> runAndWait [ "do nothing" ^ self ] + +{ #category : #mocking } +MockTestOSProcess >> workingDirectory: pathString [ + "do nothing" +] diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index e7323798..ac5d8e4e 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -21,7 +21,7 @@ To begin, you need to ensure that you have a complete Exercism development envir ### __Pharo image and IDE__ You need to have a Pharo development environment (running Pharo image - IDE) for creating the actual coding examples. -1. Use [PharoLauncher](https://pharo.org/download) to create a fresh 10.0 (stable) development image from `Official distributions` category, and launch it (you can also use [zerconf](https://get.pharo.org/) if you are familiar with it). +1. Use [PharoLauncher](https://pharo.org/download) to create a fresh stable development image from `Official distributions` category (as of 05/24 stable image is: Pharo 12.0 - 64bit (stable)), and launch it (you can also use [zerconf](https://get.pharo.org/) if you are familiar with it). 2. Launch your image to test everything runs ok. > __Note__: If you have any TIMEOUT problems refer to the [user installation instructions](./docs/INSTALLATION.md). @@ -72,7 +72,9 @@ Steps to complete exercise: ![Workflow to complete Practise exercise](/docs/images/overview-exercism-pharo-contribution.svg) ### __1. Create new Practise exercise__ -__From problem repository__ +*TLDR: All details/API description related to exercise generation can be found in class comment of `ExercismExerciseGenerator` class.* + +__New exercises from problem repository__ - If you want to start completely new Practise exercise (step 1a.), you can use problem specification repository and generate test class for given exercise by running: `ExercismExerciseGenerator generateFrom: ` - this will generate test classes for all exercises in problem specifications repository in `ExerciseWIP` package. > __Note__: You can use menu item in Pharo image for achieving same (World menu -> Exercism -> Generate test cases). @@ -80,6 +82,12 @@ __From problem repository__ Result of previous statement will be new `` (a subclass of ExercismTest) test class with generated test methods in `ExerciseWIP` package. +__Regenerate existing exercise from problem repository__ + +If you want to regenerate test methods of existing exercise (defined already in solution package - e.g. `Exercise@TwoFer`), you can use following code. Note that other methods will not be touched (e.g. uuid, version, etc.) + + `ExercismExerciseGenerator new regenerateExerciseFrom: 'path-to-problem-specifications/exercises/exercise-slug' asFileReference` + __From scratch__ If you have an interesting idea, refer to the documentation about adding new exercises. @@ -96,13 +104,15 @@ Note that: - Do not commit any configuration files or directories inside the exercise (this may be reviewed for future exercises, let us know if it becomes a problem). - Be sure to generate a new UUID for the exercise using `UUIDGenerator next`, and place that value in the `uuid` method for the test. +> __Note__: Current Pharo UUID generator is unfortunatelly not compliant with RFC 4122 - version 4. Therefore UUID generated from configlet should be used by evaluating: `/bin/configlet uuid` + ### __2. Work on existing exercise__ While there many ways to help, by far the easiest and most useful contribution is to complete a solution for any of the currently "open" exercise. * Ensure your image is caught up to the exercism/pharo-smalltalk main (and push any changes back to your fork) - * The exercises are all TestCases that been automatically generated from the aforementioned problem-specifications repository. You will find them as subclasses of ExercismTest in the ExercismWIP package. + * The exercises are all TestCases that been automatically generated from the aforementioned problem-specifications repository. You will find them as subclasses of ExercismTest in the ExercismWIP package. Note that regenerated ExercismTest might be already in given exercise package, if solution was already in place. ExercismWIP package is used for the new or work-in-progress exercises that weren't deployed yet alongside with solution class. * Once you have selected an Exercise you want to work on, create an Issue in Github specifying "Convert Exercise ". This will let others know you are working on one, and will also form a basis for your later pull request.