Skip to content

Commit

Permalink
Tooling and CI set up + 1st four exercises (#25)
Browse files Browse the repository at this point in the history
* hello-word, leap, configuration updates

Completed the hello-world and leap exercises.
Updated configuration.

* Add workflow in test.yml

* Modified CI workflow to build Odin from source

* Completed difference-of-squares, added format-all

Completed the difference-of-squares practice exercise, and added the
`bin/format-all.sh` script that recursively searches for .odin files and
runs `odinfmt` on them.

* Formatted source, added formatting to webhook

All source files have been reformatted with the `odinfmt.json` spec.
`bin/format-all.sh` is now run automatically on commit.

* Added step to push autoformatting changes

The `test.yml` GitHub workflow now pushes any changes that it makes, so
that they persist in the repo.

* Modified test.yml to not fail on no formatting

`test.yml` now only attempts a commit and push if the automated
formatting actually changed any files.

* Added missing dashes in argument

* Fixed bin/verify-exercises

The verify-exercises script now runs the individual test for both
concept and practice exercises, and is the default in the GitHub
workflow.

* Added the "grains" exercise

Tests were lifted from the Gleam track.

* Updated README; fixed test verification

- The README now contains more thorough instructions on how to add a new
  exercise.
- Fixed a variable name in the test runner that caused the "test all
  solutions" loop to run multiple times when verifying solutions.

* Formatted config files with configlet

* Fixed testing bug that clobbered stub solutions

- Fixed bin/run-test.sh so that it doesn't delete the stub solution when
  a test fails.
- Restored exercises, and added a NotImplemented error condition for the
  stubs.

* Small code fixes. Updated README.

- Ran the code formatter on source files.
- Rehaul of the README to follow Python's example.

* Added resistor-color exercise. Updated README.

* Fixed and tested the test.yml action

The workflow now uses a fixed commit hash to pull a specific release of
Odin. We now use Ubuntu 22.04 which has the libffi8 dependency, and add
clang as the only other dependency.

* Implemented the resistor-color exercise

* Removed configlet dependency in test.yml

* Moved the exercise checklist to a linked GH issue

* Test runner now checks that stub solution fails

Added a check to bin/run-test.sh so that verifies that the stub solution
does *not* pass any tests. Fixed hello-world.odin to actually be a stub.

* Fixed incorrect stubs, added panics for stub procs

- Improved bin/run-test.sh to add more visible color coding to the
  output.
- Replaced NotImplemented error codes with a compile-time panic for
  procedures that have not yet been implemented.
- Fixed stubs that were actually complete examples.

* Removed fancy terminal color commands

* Added support for skipping and unskipping tests

- Exercises' unit tests were modified so that only a minimal subset of
  tests are initially enabled; the rest are skipped.
- The test runner was modified to automatically unskip all test cases
  before running the tests.

* Removed mention of NotImplemented in the README
  • Loading branch information
gvrooyen authored Sep 2, 2024
1 parent 1365b69 commit 44aa486
Show file tree
Hide file tree
Showing 39 changed files with 1,104 additions and 245 deletions.
37 changes: 15 additions & 22 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
# This workflow will do a clean install of the dependencies and run tests across different versions
#
# Replace <track> with the track name
# Replace <image-name> with an image to run the jobs on
# Replace <action to setup tooling> with a github action to setup tooling on the image
# Replace <install dependencies> with a cli command to install the dependencies
#
# Find Github Actions to setup tooling here:
# - https://github.com/actions/?q=setup&type=&language=
# - https://github.com/actions/starter-workflows/tree/main/ci
# - https://github.com/marketplace?type=actions&query=setup
#
# Requires scripts:
# - bin/test

name: <track> / Test
# This workflow creates an Odin environment and runs all tests for all exercises.

name: odin/Test

on:
push:
Expand All @@ -23,17 +10,23 @@ on:

jobs:
ci:
runs-on: <image-name>
runs-on: ubuntu-22.04

steps:
- name: Update packages
run: sudo apt update

- name: Install Clang
run: sudo apt -y install clang

- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332

- name: Use <setup tooling>
uses: <action to setup tooling>

- name: Install project dependencies
run: <install dependencies>
- name: Setup Odin
uses: laytan/setup-odin@41f9612bfec760bbb68b05b5747f319afe7c48d8
with:
token: ${{ secrets.GITHUB_TOKEN }}
llvm-version: 14

- name: Verify all exercises
run: bin/verify-exercises
210 changes: 106 additions & 104 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,106 +1,108 @@
# Odin

Official Exercism forum thread about this track: https://forum.exercism.org/t/new-track-odin-programming-language/7379

Borring concepts from other C-based/C-adjacent language tracks:
- https://github.com/exercism/c
- https://github.com/exercism/zig

## TODO

- Figure out how to build an Odin test runner (currently using bash script for this)
- [Highlight.js support for Odin](https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md)

## Odin Docs

- [Odin website](http://odin-lang.org)
- [Odin GitHub](https://github.com/odin-lang/Odin)
- [Odin examples](https://github.com/odin-lang/examples)
- [Odin language server](https://github.com/DanielGavin/ols)

## Exercism Docs

- https://exercism.org/docs/building/tracks/new/request-new
- https://exercism.org/docs/building/tracks/new/add-first-exercise
- https://exercism.org/docs/building/tracks/new/add-initial-exercises
- https://exercism.org/docs/building/tracks/new/setup-continuous-integration
- https://exercism.org/docs/building/tooling/test-runners
- https://github.com/exercism/generic-track
- https://github.com/exercism/problem-specifications

## Example Nix Config

```nix
{ pkgs }:
let
inherit (pkgs) lib;
# TODO: Building odinfmt requires the nighly build of Odin itself
new_pkgs = import
(pkgs.fetchFromGitHub {
owner = "NixOS";
repo = "nixpkgs";
rev = "ef66aec42b5f9035a675496e9a7fe57b63505646";
# sha256 = lib.fakeSha256;
sha256 = "1j1ywwk1wzcd60dbam3pif8z0v695ssmm8g4aw9j01wl36pds31a";
})
{ };
odin = new_pkgs.odin;
in
{
deps = [
odin
pkgs.ruby
pkgs.gh
pkgs.just
pkgs.jq
];
}
```

*Below is the previous generic track readme; will modify later.*

---

# Exercism Odin Track

[![Configlet](https://github.com/exercism/odin/actions/workflows/configlet.yml/badge.svg)](https://github.com/exercism/odin/actions/workflows/configlet.yml) [![.github/workflows/test.yml](https://github.com/exercism/odin/actions/workflows/test.yml/badge.svg)](https://github.com/exercism/odin/actions/workflows/test.yml)

Exercism exercises in Odin.

## Testing

To test the exercises, run `./bin/test`.
This command will iterate over all exercises and check to see if their exemplar/example implementation passes all the tests.

### Track linting

[`configlet`](https://exercism.org/docs/building/configlet) is an Exercism-wide tool for working with tracks. You can download it by running:

```shell
./bin/fetch-configlet
```

Run its [`lint` command](https://exercism.org/docs/building/configlet/lint) to verify if all exercises have all the necessary files and if config files are correct:

```shell
$ ./bin/configlet lint

The lint command is under development.
Please re-run this command regularly to see if your track passes the latest linting rules.

Basic linting finished successfully:
- config.json exists and is valid JSON
- config.json has these valid fields:
language, slug, active, blurb, version, status, online_editor, key_features, tags
- Every concept has the required .md files
- Every concept has a valid links.json file
- Every concept has a valid .meta/config.json file
- Every concept exercise has the required .md files
- Every concept exercise has a valid .meta/config.json file
- Every practice exercise has the required .md files
- Every practice exercise has a valid .meta/config.json file
- Required track docs are present
- Required shared exercise docs are present
```
<br>

Hi. &nbsp;👋🏽 &nbsp;👋 &nbsp;**We are happy you are here.**&nbsp; 🎉&nbsp;🌟

<br>

**`exercism/odin`** is one of many programming language tracks on [Exercism](exercism-website).
This repo holds all the instructions, tests, code, and support files for Odin _exercises_ currently under development or implemented and available for students.

🌟 &nbsp;&nbsp;Track exercises support the `dev-2024-08` release of Odin.

Exercises are grouped into **concept** exercises which teach the Odin syllabus, which will eventually live [here][odin-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree &nbsp;🌴&nbsp;.
Concept exercises are constrained to a small set of language or syntax features.
Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. These two exercise groupings can be found in the track [config.json][config-json], and under the `odin/exercises` directory.

<br><br>

<div>
<span>
<img align="left" height="60" width="85" src="https://user-images.githubusercontent.com/5923094/204436863-2ebf34d1-4b16-486b-9e0a-add36f4c09c1.svg">
</span>
<span align="left">

🌟🌟&nbsp; Please take a moment to read our [Code of Conduct][exercism-code-of-conduct]&nbsp;🌟🌟&nbsp;
It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member] & [The words that we use][the-words-that-we-use].

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins]

</span></div>

<br>
<img align="left" width="95" height="90" src="https://github.com/exercism/website-icons/blob/main/exercises/boutique-suggestions.svg">

Here to suggest a new feature or new exercise?? **Hooray!** &nbsp;🎉 &nbsp;
We'd love if you did that via our [Exercism Community Forum][exercism-forum] where there is a [dedicated thread][odin-thread] for the new Odin track.
Please read [Suggesting Exercise Improvements][suggesting-improvements] & [Chesterton's Fence][chestertons-fence].
_Thoughtful suggestions will likely result in faster & more enthusiastic responses from volunteers._

<br>
<img align="left" width="85" height="80" src="https://github.com/exercism/website-icons/blob/main/exercises/word-search.svg">

&nbsp;🦄&nbsp; _**Want to jump directly into Exercism specifications & detail?**_
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Structure][exercism-track-structure] **|** [Tasks][exercism-tasks] **|** [Concepts][exercism-concepts] **|** [Concept Exercises][concept-exercises] **|** [Practice Exercises][practice-exercises] **|** [Presentation][exercise-presentation]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Writing Style Guide][exercism-writing-style] **|** [Markdown Specification][exercism-markdown-specification] (_✨ version in [contributing][website-contributing-section] on exercism.org_)

<br>
<br>

## Contributing an Exercise
If you are interested in contributing a new exercise, please have a look at [this issue][odin-backlog] to see which exercises are waiting on implementation.
Leave a comment in the issue to notify other contributors which exercise you plan to implement.

The `bin/` subdirectory contains several scripts to help you contribute exercises that will run correctly on Exercism:

- `configlet` is a tool to help track maintainers with the maintenance of their track.
Fetch it by running the `bin/fetch-configlet` script.
Run `bin/configlet lint` to verify that the track is properly structured.
- `bin/fetch-ols-odinfmt.sh` will fetch the Odin language server (`ols`) that can assist with verifying Odin code directly in your IDE.
`odinfmt` is a tool that can format Odin code according to the specification in `odinfmt.json`.
Please run `odinfmt` before pushing your changes to the repository.
whenever new code is pushed to the repository.
- `bin/format-all.sh` will run `odinfmt` on all `.odin` files in the repository.
- `bin/run-test.sh` runs the tests for a specific exercise, or for all exercises if no exercise name is provided.
- `bin/verify-exercises` checks the integrity of all exercises, including tests.
It is used by the build system whenever new code is pushed to the repository.
- `bin/configlet` can be used to generate a new exercise. More details follow below.

### Creating a New Exercise
- Run `bin/configlet create --practice-exercise <slug>` to automatically generate the exercise skeleton in the `exercises/practice/<slug>/` directory and to update `config.json` to reference the new exercise.
You can add `--author <your_exercism_username>` as option to mark yourself as the creator of this exercise (or add it later in the exercise's `.meta/config.json` file.)
- Add a solution stub at the exercise's `<slug>.odin` file.
This is what students will begin with when they start the exercise.
It should make it as easy as possible to understand what they need to solve, without revealing too much of the solution.
Stub functions should usually panic, e.g. `#panic("Please implement the <stub> function.")`.
- Add tests to `<slug>_test.odin`.
Verify that the slug solution would fail _all_ tests.
- Implement a reference solution at `.meta/<slug>_example.odin`.
- Use `bin/run_test.sh <slug>` to verify that your reference solution passes.

[being-a-good-community-member]: https://github.com/exercism/docs/tree/main/community/good-member
[chestertons-fence]: https://github.com/exercism/docs/blob/main/community/good-member/chestertons-fence.md
[concept-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md
[config-json]: https://github.com/exercism/odin/blob/main/config.json
[exercise-presentation]: https://github.com/exercism/docs/blob/main/building/tracks/presentation.md
[exercism-admins]: https://github.com/exercism/docs/blob/main/community/administrators.md
[exercism-code-of-conduct]: https://exercism.org/docs/using/legal/code-of-conduct
[exercism-concepts]: https://github.com/exercism/docs/blob/main/building/tracks/concepts.md
[exercism-contributors]: https://github.com/exercism/docs/blob/main/community/contributors.md
[exercism-forum]: https://forum.exercism.org/
[exercism-markdown-specification]: https://github.com/exercism/docs/blob/main/building/markdown/markdown.md
[exercism-mentors]: https://github.com/exercism/docs/tree/main/mentoring
[exercism-tasks]: https://exercism.org/docs/building/product/tasks
[exercism-track-maintainers]: https://github.com/exercism/docs/blob/main/community/maintainers.md
[exercism-track-structure]: https://github.com/exercism/docs/tree/main/building/tracks
[exercism-website]: https://exercism.org/
[exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md
[freeing-maintainers]: https://exercism.org/blog/freeing-our-maintainers
[practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md
[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md
[odin-backlog]: https://github.com/exercism/odin/issues/26
[odin-release]: https://github.com/odin-lang/Odin/releases/tag/dev-2024-08
[odin-syllabus]: https://exercism.org/tracks/odin/concepts
[odin-thread]: https://forum.exercism.org/t/new-track-odin-programming-language/7379
[suggesting-improvements]: https://github.com/exercism/docs/blob/main/community/good-member/suggesting-exercise-improvements.md
[the-words-that-we-use]: https://github.com/exercism/docs/blob/main/community/good-member/words.md
[website-contributing-section]: https://exercism.org/docs/building
4 changes: 2 additions & 2 deletions bin/fetch-ols-odinfmt.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash

# version="refs/heads/master"
version="46892948312c14b44600ae9f557e86bd8c792343"
version="e2f4f96cd46b70360f3caa58acc4af14eb0e8688"
bin_dir="bin"
name="ols"

Expand Down Expand Up @@ -35,4 +35,4 @@ mv odinfmt ..
popd > /dev/null

rm -rf $tarball_dir
rm -f $tarball_path
rm -f $tarball_path
6 changes: 6 additions & 0 deletions bin/format-all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

# Runs `odinfmt` on all .odin files in the current tree.

find . -type f -name "*.odin" -exec odinfmt -w {} \;
echo "All Odin files have been formatted."
6 changes: 3 additions & 3 deletions bin/gen-exercise.sh
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ EOL
# Make the example file a simple copy of the solution file
cp ${solution_file} ${example_file}

echo "Formatting new Odin files:"
bin/odinfmt -w ${exercises_path}
# echo "Formatting new Odin files:"
# bin/odinfmt -w ${exercises_path}

echo "Be sure to implement the following files:"
echo -e "\t${solution_file}"
Expand All @@ -110,4 +110,4 @@ EOL

echo "Running configlet lint:"
bin/configlet lint
fi
fi
61 changes: 47 additions & 14 deletions bin/run-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ function run_test() {
meta=".meta"
exercise_name="${1}"
exercise_path="${exercises_path}/${exercise_name}"
tmp_path=`mktemp -d`

echo "$exercise_name / $exercise_path"

if [ -n "${exercise_name}" ] && [ -d "${exercise_path}" ]; then
echo "Running test for exercise: ${exercise_name}"
echo -e "Running test for exercise: ${exercise_name}\n"

# Turn something like "hello-world" into "hello_world"
exercise_safe_name=$(echo $exercise_name | to_snake_case)
Expand All @@ -31,20 +34,42 @@ function run_test() {
# "exercises/practice/.meta/hello_world_example.odin"
example_file="${exercise_path}/${meta}/${exercise_safe_name}_example.odin"

# Move the blank solution file into the meta directory for a bit
mv ${solution_file} ${exercise_path}/${meta}

# Copy the example file into the main directory
cp ${example_file} ${solution_file}

# Run the tests using the example file
odin test ${exercise_path}
# Copy the example file into the temporary directory
cp ${example_file} ${tmp_path}/${exercise_safe_name}.odin

# Unskip all tests and write the processed test file to the temporary directory.
# The test file for the exercise often has several of the tests skippped initially, so that
# students can do test-driven development by enabling the next test, possibly see it fail,
# and then refining their solution. However, the test runner used by contributors and the CI
# pipeline always needs to run all tests.
#
# In Odin, a test can be skipped by commenting out the `@(test)` annotation preceding the
# test procedure. Here we unskip the test by searching for `\\ @(test)` lines and replacing
# them with `@test`.
sed s/"\/\/ @(test)"/"@(test)"/ ${test_file} > ${tmp_path}/${exercise_safe_name}_test.odin

# Move the blank solution file back into the main directory
mv "${exercise_path}/${meta}/${exercise_safe_name}.odin" ${solution_file}
# Run the tests using the example file to verify that it is a valid solution.
odin test ${tmp_path}

echo -e "Checking that the stub solution *fails* the tests\n"

# Copy the stub solution to the temporary directory
cp ${solution_file} ${tmp_path}/${exercise_safe_name}.odin

# Run the test. If it passes, exit with a message and an error.
# TODO: Check that the stub fails _all_ the tests.
# We only check for a single failed test here -- the stub solution could solve all the cases
# but only fail on the most complicated one. Since the purpose of this test is mostly to
# double-check that the example didn't accidentally get duplicated as the stub, this isn't
# too critical for now.

if odin test ${tmp_path} ; then
echo -e '\nERROR: The stub solution must not pass the tests!\n'
exit 1
else
echo -e '\nSUCCESS: The stub solution failed the tests above as expected.\n'
fi

# Remove the built executable
rm -f ${exercise_name}
else
echo "Running all tests"
for exercise in $(ls $exercises_path)
Expand All @@ -54,4 +79,12 @@ function run_test() {
fi
}

run_test $@
# Delete the temp directory
function cleanup {
rm -rf ${tmp_path}
}

# Register the cleanup function to be called on the EXIT signal
trap cleanup EXIT

run_test $@
Loading

0 comments on commit 44aa486

Please sign in to comment.