Skip to content

Commit

Permalink
Put exercise generation under test (#426)
Browse files Browse the repository at this point in the history
* Put exercise generation under test

This change alters `ExercismGenerator` just enough to allow side effect free
tests. Having the class under test enables future refactoring. Three dependencies
made this class difficult to test:
- `PipeableOSProcess` executed commands outside of the Pharo VM
- `ExTonelWriter` wrote files to the OS filesystem
- `ExercismExercise` would access a variable number of exercises depending on how many were implemented

To get this class under test the above dependencies have been placed in lazy
assigned instance variables. This allows tests to assign mocks to the generator
instance.

There are two mock OS processes, one that always succeeds, and one that always fails.
Neither executes any function outside the VM.

A  `ExTonelWriter` writes exercises to an in memory file system that lasts only
for the test.

A mock exercism exercise class returns only a single exercise. This speeds up the
test and makes it more consistent as it won't change when more exercises are implemented.

## Test Issues with Line Endings

When run in CI (Ubuntu 16.04) one of the tests failed, but the same test passed on my personal computer (MacOS).

I suspect that even though the tests use an in memory file system, OS specific line-feed and carriage-return
characters will be used. This causes enough of a difference between the expected result and the actual
result that the outcome will be a failed test. Each test that works with an in memory file system will normalize the
line endings of the expected result and the actual result to the same character. This should eliminate this kind of
environment specific failure in tests.

The test design closely follows that of `TonelWriterTest` which also uses an in memory file system.
  • Loading branch information
samWson authored Oct 20, 2020
1 parent 725c802 commit 2ac04ea
Show file tree
Hide file tree
Showing 6 changed files with 456 additions and 20 deletions.
112 changes: 107 additions & 5 deletions dev/src/ExercismDev/ExercismGenerator.class.st
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
"
I am responsible for generating kebab-cased Exercism edirectories, each containing a seperate exercise for users.
# ExercismGenerator
We have to do it this way as Exercism conventions differ from Tonel, and so we need to output them to a seperate directory suitable for the Exercism command line tool.
I am responsible for generating kebab-cased Exercism V2 directories, each containing a seperate exercise for users.
Exercism requires plain text files that can be served to the website as exercises and then downloaded for a student
to work on. We have to do it this way as Exercism conventions differ from Tonel, and so we need to output them to a
seperate directory suitable for the Exercism command line tool.
## Collaborators
I need two other key objects to do my job: an `ExTonelWriter` and a `PipeableOSProcess`. `ExTonelWriter` does the
actual job of writing Tonel format files to disk. `PipeableOSProcess` is needed to make a external command line
call to the configlet tool.
To grab the exercises that need to be written I use `ExercismExercise`.
## Usage
When you have created a new exercise(s) that you are happy with, you need to run a generation.
You can either click on the #generate example method and be prompted for a file location or evaluate the following (where the path is one that points to where you have checked out the entire pharo project using either the command line or a git tool):
You can either run the `ExercismGenerator class>>#generate` method and be prompted for a file location or evaluate
the following (where the path is one that points to where you have checked out the entire pharo project using either
the command line or a git tool):
```
ExercismGenerator generateTo: (FileLocator home / 'Dev/Exercism/pharo-git') pathString
```
## Instance Variables
- exercismExercise: A class handle for easily getting all the implemented exercises. Defaults to `ExercismExercise`.
- exTonelWriter: `ExTonelWriter`; Responsible for writing objects as Tonel format source text files to the give file path.
- osProcess: A class handle of an operating system process, for sending external commands.
## Class Variables
- DefaultPath: The default file path that Tonel files will be written to.
"
Class {
#name : #ExercismGenerator,
#superclass : #Object,
#instVars : [
'exTonelWriter',
'osProcess',
'exercismExercise'
],
#classVars : [
'DefaultPath'
],
Expand Down Expand Up @@ -55,6 +88,58 @@ ExercismGenerator >> createTagSnapshotFor: packageOrTag [
[ :mc | mc className isNil or: [ mc actualClass category endsWith: packageOrTag name ] ])
]

{ #category : #accessing }
ExercismGenerator >> exTonelWriter [

^ exTonelWriter
ifNotNil: [ exTonelWriter ]
ifNil: [
exTonelWriter := ExTonelWriter.
exTonelWriter
]
]

{ #category : #accessing }
ExercismGenerator >> exTonelWriter: anExTonelWriter [

exTonelWriter := anExTonelWriter
]

{ #category : #accessing }
ExercismGenerator >> exercismExercise [

^ exercismExercise
ifNotNil: [ exercismExercise ]
ifNil: [
exercismExercise := ExercismExercise.
ExercismExercise
]
]

{ #category : #accessing }
ExercismGenerator >> exercismExercise: anExerciseClass [

exercismExercise := anExerciseClass
]

{ #category : #generation }
ExercismGenerator >> generate [
| cmd result basePathReference |

self exercismExercise allExercises select: [:ex | ex isActive ] thenDo: [:ex |
self generateSourceFilesFor: ex exercisePackage to: self class defaultPath ].

basePathReference := self class defaultPath parent.
ExercismConfigGenerator generateTo: basePathReference.

cmd := 'configlet generate ', (basePathReference pathString surroundedBySingleQuotes).
result := self osProcess waitForCommand: cmd.

result succeeded
ifFalse: [
self error: 'failure running "configlet generate" - ' , result outputAndError printString ]
]

{ #category : #helper }
ExercismGenerator >> generateCustomDataFor: anExercismExercise to: destinationDirectory [
"Generate markdown hints, that exercism configlet will pickup for readme.md files
Expand Down Expand Up @@ -114,8 +199,8 @@ ExercismGenerator >> generateSourceFilesFor: packageOrTag to: filePathString [
exerciseDirectoryRef ensureCreateDirectory.
exerciseDirectoryRef deleteAll.
(ExTonelWriter on: exampleDirectoryRef)
self exTonelWriter
sourceDirectory: (solutionDirectoryRef relativeTo: exampleDirectoryRef) pathString;
writeSnapshot: (self createTagSnapshotFor: packageOrTag).
Expand Down Expand Up @@ -154,3 +239,20 @@ ExercismGenerator >> generateTo: filePathReference [
ifFalse: [
self error: 'failure running "configlet generate" - ' , result outputAndError printString ]
]
{ #category : #accessing }
ExercismGenerator >> osProcess [
^ osProcess
ifNotNil: [ osProcess ]
ifNil: [
osProcess := PipeableOSProcess.
osProcess
]
]
{ #category : #accessing }
ExercismGenerator >> osProcess: anOsProcess [
osProcess := anOsProcess
]
Loading

0 comments on commit 2ac04ea

Please sign in to comment.