Skip to content

Commit

Permalink
Add ci and more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
meatball133 committed Nov 9, 2024
1 parent 77a991b commit 039911c
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 14 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/generator-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: GeneratorTests
on:
pull_request:
push:
branches:
- main
schedule:
# Weekly.
- cron: '0 0 * * 0'

jobs:
test-generator-templates:
name: Test Generator
runs-on: ubuntu-22.04
steps:
- name: Set up Ruby
uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999
with:
ruby-version: "3.3"
bundler-cache: true
run: ruby ./bin/generator.rb --verify
test-generator:
name: Check Generator Templates
runs-on: ubuntu-22.04
container:
image: crystallang/crystal
steps:
- name: Set up Ruby
uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999
with:
ruby-version: "3.3"
bundler-cache: true
run: rake test:generator
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ gem 'rubocop-minitest', require: false
gem 'rubocop-rake', require: false
gem 'simplecov', require: false
gem 'racc', require: false
gem 'toml-rb', require: false
5 changes: 5 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,10 @@ namespace :test do
task.pattern = 'test/**/*_test.rb'
end

Rake::TestTask.new :generator do |task|
task.options = flags
task.pattern = 'generatorv2/test/**/*_test.rb'
end

ExerciseTestTasks.new options: flags
end
10 changes: 8 additions & 2 deletions bin/generate.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'optparse'
require 'tempfile'
require_relative '../generatorv2/lib/generator'

parser = OptionParser.new
Expand All @@ -23,8 +24,13 @@
parser.on('--verify', 'Verify all exercises') do
exercises = Dir.entries('./exercises/practice').select { |f| File.directory? File.join('./exercises/practice', f) }
exercises.each do |exercise|
puts "Verifying #{exercise}"
system("ruby ./exercises/practice/#{exercise}/#{exercise}__test.rb")
if File.exist?("./exercises/practice/#{exercise}/.meta/test_template.erb")
current_code = File.read("./exercises/practice/#{exercise}/#{exercise}_test.rb")
f = Tempfile.create
Generator.new(exercise).generate(f.path)
generated_code = f.read
raise RuntimeError.new("The result generated for: #{exercise}, doesnt match the current file") if current_code != generated_code
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion exercises/practice/acronym/acronym_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_punctuation_without_whitespace
def test_very_long_abbreviation
skip
assert_equal 'ROTFLSHTMDCOALM',
Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me')
Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me')
end

def test_consecutive_delimiters
Expand Down
139 changes: 139 additions & 0 deletions generatorv2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Generator

Last Updated: 2024/11/9

The generator is a powerful tool that can be used to generate tests for exercises based on the canonical data.
The generator is written in Ruby and is located in the `bin` directory.

## How to use the generator

### Things to do before running the generator

Before running the generator you have to make sure a couple of files are in place.

1. `tests.toml` file

It is located under the `.meta` folder for each exercise.
The toml file is used to configure which exercises are generated and which are not.
Since the generator grabs all the data from the canonical data, so does this enable new tests that won't automatically be merged in.
Instead so does new tests have to be added to the toml file before they show up in the test file.

If there is a test that isn't needed or something that doesn't fit Ruby you can remove it from the toml file.
By writing after the test name `include = false` and it will be skipped when generating the test file.

2. `config.json` file, located in the root of the track

The generator makes sure that the exercise is in the config.json so you need to add it there before running the generator.

3. `spec` directory

The generator will create a spec file for each exercise, so you need to make sure that the spec directory is in place.
Although there don't have to be any files in the directory, since the script will create one for you.
If it is a file already so will the generator overwrite it.

#### Things to note

The script which grabs info from the toml file is quite sensitive, writing the toml file in an incorrect way can brick the generator.

Here are some examples of how you should **NOT** work with the toml file.

Make sure that the uuid is the only thing inside of `[uuid]`, if there is, for example, an extra space so would that break it.
Here is an example

```toml
# This would break it since it is an extra space between uuid and `]`
[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 ]
# This would break it since it is an extra space between uuid and `[`
[ 1e22cceb-c5e4-4562-9afe-aef07ad1eaf4]
```

The script won't care if you write `include = true` since if it sees the uuid it will always take it as long as `include = false` is not written.
The script will not work if anything is misspelled, although the part which gets `include = false` doesn't care if it gets an extra space or not.

**NOTE:**
You are also **NOT** allowed to write `include = false` more than once after each uuid.
Since that can lead to errors in the generator.

Bad way:

```toml
[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4]
description = "basic"
include = false
include = false
```

Good way:

```toml
[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4]
description = "basic"
include = false
```

### Template

The generator uses a template file to generate the test file.
The template is located under the `.meta` for each exercise.

This template has to be manually written for each exercise.
The goal although is to make it so that you only have to write the template once and then it will be able to be used to generate new tests.

The template file is written in [Embedded Ruby(ERB)][erb].
ERB enables you to write Ruby code inside of the template file.
It also means that the templates can be highly customizable since you can write any Ruby code you want.

When writing the template file it is recommended to look at already existing template files to get a better understanding of how it works.
The template is getting a slightly modified version of the canonical data, so you can check out the [canonical data][canonical data] to see the data structure.
The modification is that the cases which are not included in the toml file will be removed from the data structure.

When writing the template so is it a special tool that can help with giving `# skip` and `skip` tags for tests.
You simply have to call the `status` method.
It will return either `# skip` or `skip` depending on if it is the first test case or not.

Here is an example:

```
<%= status()%>
<%= status()%>
<%= status()%>
```

result:

```
# skip
skip
skip
```

### The Test Generator

If all the earlier steps are done so can you run the generator.
To run the generator you need to have a working Ruby installation and installed all gems in the Gemfile.
The generator is located in the `bin` directory and is called `generator.rb`.

To run the generator so do you have to be in the root directory and run the following command:

```shell
ruby ./bin/generator.rb -e <exercise_slug>
```

Where `<exercise_slug>` is the same name as the slug name which is located in the `config.json` file.

For more commands so can you run the following command:

```shell
ruby ./bin/generator.rb --help
```

### Errors and warnings

The generator will give you errors and warnings if something is wrong.
That includes if the exercise is not in the `config.json` file, if the exercise is not in the toml file, or if the template file is missing.
Also if it has a problem getting the `canonical-data.json` file so will it give you an error.
The generator also uses a formatter which will give you errors if the generated file is not formatted correctly.
The file will still be generated even if formatter gives errors, therefore can you check the file and see what is wrong and fix it in the template.

[erb]: https://docs.ruby-lang.org/en/master/ERB.html
[canonical data]: https://github.com/exercism/problem-specifications
14 changes: 6 additions & 8 deletions generatorv2/lib/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def initialize(exercise = nil)
@exercise = exercise
end

def generate
def generate(result_path = "./exercises/practice/#{@exercise}/#{@exercise}_test.rb")
json = get_remote_files
uuid = toml("./exercises/practice/#{@exercise}/.meta/tests.toml")
additional_json(json)
Expand All @@ -23,14 +23,14 @@ def generate

result = template.result(binding)

File.write("./exercises/practice/#{@exercise}/#{@exercise}_test.rb", result)
File.write(result_path, result)
cli = RuboCop::CLI.new
cli.run(['-x', "--force-default-config", "exercises/practice/#{@exercise}/#{@exercise}_test.rb"])
cli.run(['-x', "--force-default-config", "-o", "/dev/null", result_path])
end

def underscore(str)
str.each_char.reduce("") do |acc, x|
acc << if x == ' '
acc << if [' ', '-'].include?(x)
'_'
else
x.downcase
Expand All @@ -40,7 +40,7 @@ def underscore(str)
end

def camel_case(str)
str.split('_').map(&:capitalize).join
str.split(/[-_]/).map(&:capitalize).join
end

def status
Expand All @@ -56,7 +56,7 @@ def toml(path = "./exercises/practice/#{@exercise}/.meta/tests.toml")

uuid = TomlRB.load_file(path)
uuid.filter do |_k, v|
!v.any? { |k, v| k == "include" && !v }
v.none? { |k, v| k == "include" && !v }
end.map { |k, _v| k }
end

Expand Down Expand Up @@ -99,5 +99,3 @@ def remove_tests(uuid, json)
end
end
end

# Generator.new("acronym").get_remote_files
6 changes: 3 additions & 3 deletions generatorv2/test/toml_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
class GeneratorTest < Minitest::Test
def test_importning_toml
assert_equal %w[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 79ae3889-a5c0-4b01-baf0-232d31180c08 ec7000a7-3931-4a17-890e-33ca2073a548 32dd261c-0c92-469a-9c5c-b192e94a63b0 ae2ac9fa-a606-4d05-8244-3bcc4659c1d4 0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9 6a078f49-c68d-4b7b-89af-33a1a98c28cc],
Generator.new("two_fer").toml("./test/misc/tests.toml")
Generator.new("two_fer").toml("generatorv2/test/misc/tests.toml")
end

def test_importing_toml_with_no_include
assert_equal %w[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 79ae3889-a5c0-4b01-baf0-232d31180c08 ec7000a7-3931-4a17-890e-33ca2073a548 32dd261c-0c92-469a-9c5c-b192e94a63b0 ae2ac9fa-a606-4d05-8244-3bcc4659c1d4 6a078f49-c68d-4b7b-89af-33a1a98c28cc],
Generator.new("two_fer").toml("./test/misc/tests_no_include.toml")
Generator.new("two_fer").toml("generatorv2/test/misc/tests_no_include.toml")
end

def test_importing_toml_with_all_excluded
assert_empty Generator.new("two_fer").toml("./test/misc/tests_all_excluded.toml")
assert_empty Generator.new("two_fer").toml("generatorv2/test/misc/tests_all_excluded.toml")
end
end
36 changes: 36 additions & 0 deletions generatorv2/test/utils_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require_relative '../lib/generator'
require 'minitest/autorun'

class UtilTest < Minitest::Test
def test_camelize
assert_equal "Acronym",
Generator.new("acronym").camel_case("acronym")
end

def test_camelize_with_two_words
assert_equal "TwoFer",
Generator.new("two-fer").camel_case("two-fer")
end

def test_underscore
assert_equal "acronym",
Generator.new("acronym").underscore("acronym")
end

def test_underscore_with_two_words
assert_equal "two_fer",
Generator.new("two-fer").underscore("two-fer")
end

def test_status
assert_equal "# skip",
Generator.new("acronym").status
end

def test_status_after_first
generator = Generator.new("acronym")
generator.status
assert_equal "skip",
generator.status
end
end

0 comments on commit 039911c

Please sign in to comment.