Skip to content

A simple library that makes running and testing your Kotlin solutions to Advent of Code puzzles a breeze.

License

Notifications You must be signed in to change notification settings

Jadarma/advent-of-code-kotlin

Repository files navigation

Advent of Code - Kotlin (AocKt)

Kotlin Kotest Build Status Maven Central Snapshot

AocKt (short for Advent of Code - Kotlin) is a simple library that makes running and testing your Kotlin solutions to Advent of Code puzzles a breeze.

It is an opinionated testing framework built on Kotest that defines a new AdventSpec specialized for testing AoC puzzle solutions with minimal boilerplate.

✨ Features

  • Completely Offline - Puzzle inputs and solutions are read from local files, no need for tokens.
  • Test Driven - Run your code from unit tests for faster feedback loops and fearless refactorings.
  • DSL Driven - Define your test cases with minimal code.
  • Configurable - You decide what runs and when using optional parameters.
  • Minimal - The test framework is the only non-Kotlin dependency.

⚡ Quick Start

Gradle

To use AocKt, simply add the dependencies and configure your project to run unit tests with Kotest:

repositories {
    mavenCentral()
}

dependencies {
    implementation("io.github.jadarma.aockt:aockt-core:$aocktVersion")
    testImplementation("io.github.jadarma.aockt:aockt-test:$aocktVersion")
    testImplementation("io.kotest:kotest-runner-junit5:5.8.1")
}

tasks.test {
    useJUnitPlatform()
}

Project Template

For your convenience, there is an advent-of-code-kotlin-template repository which you can use to generate your own solutions repo, featuring a pre-configured Gradle project, modified source directory locations for easier navigation, and a detailed README with workflow examples. If you need a working example, you can check out my solutions repo as well.

📄 Usage Guide

Basic Test Definition
object Y9999D01 : Solution { /* ... */ }                // 1.

@AdventDay(9999, 1, "Magic Numbers")                    // 2.
class Y9999D01Test : AdventSpec<Y9999D01>({             // 3.
    partOne {                                           // 4.
        "1,2,3,4" shouldOutput 4                        // 5.
        listOf("2", "2,2", "2,4,6,8") shouldAllOutput 0 // 6.
    }
    partTwo()                                           // 7.
})

In the above example:

  1. Your solution should implement the Solution interface.
  2. Each test class should be annotated with the @AdventDay annotation. Title is optional, but the year and day are required, so the spec knows what user input to test with.
  3. Rather than passing it as an instance, the AdventSpec takes in your solution as a type parameter.
  4. Use the partOne and partTwo functions as needed. Inside the lambda you can define test cases. The Solution functions will only be invoked if the relevant part DSL is used. If you have not yet implemented the second part, or it doesn't exist (e.g.: Every year, part two of the last day just requires collecting all other 49 stars), then you may simply omit it.
  5. To define a test case, use the shouldOutput function. Each usage will define another test case. The value tested against is checked against its string value, so shouldOutput 4 and shouldOutput "4" are equivalent.
  6. As a shorthand for defining multiple examples that should output the same thing, use the shouldAllOutput function.
  7. If you don't have any examples, but do want to run the part against your input the lambda can be omitted.
Project Configuration

AocKt provides an extension you may register in your Kotest project to tweak the behaviour of the AdventSpec. Registering it is optional but recommended, and can be done like any other extension:

object TestConfig : AbstractProjectConfig() {

    override fun extensions() = listOf<Extension>(
        AocKtExtension(),
    )
}

The following optional parameters exist:

  • formatAdventSpecNames - When the extension is registered, AdventSpec classes have a pretty formatted name in the test runner. Set this to false to opt out of this behavior.
  • efficiencyBenchmark - If a solution completes under this time value, it will pass its efficiency test. Lower this value to increase the challenge or increase it to adjust for your hardware (the latter shouldn't be necessary). Can be overridden for individual parts, see Execution Configuration for Parts for more details. Default is fifteen seconds.
  • executionMode - Choose the default execution mode for the entire project (run only examples, only user input, or all of them). If set to ExamplesOnly, does not run against the true puzzle input even if present. Useful when running the project with encrypted inputs (e.g. running a clone of someone else's solution repo). If set to SkipExamples, will only test against user input. Can be overridden for individual parts, see Execution Configuration for Parts for more details. Default is all.
Testing Against User Input

By default, only the example defined in the DSL will run. However, the solution can be tested against real user input if it is detected. AocKt looks inside the test resources directory for them. The structure is fixed and must match the following:

testResourcesDir
  └──  aockt
     └── y{year}
        └── d{two-digit-day}
           └── input.txt
           └── solution_part1.txt
           └── solution_part2.txt

If the input.txt file exists, the Solution will be ran against it after the example tests. It is normal that at first the solutions are unknown, and therefore missing. The unverified solution will be added in parens at the end of the test name, which you can then submit to the website.

If the solution_part1 or solution_part2.txt exist, then the value contained within them is assumed to be the correct output when running against input.txt, and will be validated.

IMPORTANT!: The reason for keeping the user inputs separate from the tests (apart from readability) is that puzzle inputs should not be redistributed. If you plan on sharing your solutions repository publicly, either .gitignore the src/test/resources/aockt directory or commit them as encrypted blobs only you can read!

Execution configuration for Parts

The partOne and partTwo scopes can be configured with optional parameters. These allow you to modify the way the generated tests behave. They are mostly meant to help during development, and reverted once your solutions are complete.

The following optional parameters exist:

  • enabled - True by default. When set to false, all tests for this part are ignored. Useful for when working on the second part of a puzzle and want to skip re-checking the first when running the spec.

  • expensive - False by default. Lowers the time restrictions for execution. While most puzzles should be solvable in under 15 seconds, sometimes it's hard to come up with an optimised solution on the first try. This option tags the tests as slow if you want to exclude them from bulk execution.

  • executionMode - Defaults to project configuration. If set to ExamplesOnly, does not run against the true puzzle input even if present. Useful when refactoring an expensive day and no not wish to waste time on the big test while the small ones do fail. If set to SkipExamples, does not run against the example test cases even if present. Useful for isolating a single execution of the solution, useful when debugging.

  • efficiencyBenchmark - Defaults to project configuration. The maximum runtime a solution can have while being considered efficient by the time tests. Only the user input tests are measured.

Multiple Solutions for the Same Puzzle

The AdventSpec is designed to test a single Solution at a time. However, that doesn't mean you need to duplicate the code if you want to show off multiple approaches to the solution! You can instead define an abstract specification for your test cases, and use it to derive an arbitrary number of specific implementation test classes, and supply the variant parameter to the AdventDay annotation to disambiguate between the two.

object SolutionA : Solution { /* ... */ }
object SolutionB : Solution { /* ... */ }

@AdventDay(9999, 1, "Magic Numbers", "Variant A")
class SolutionATest : Y9999D01Spec<SolutionA>()

@AdventDay(9999, 1, "Magic Numbers", "Variant B")
class SolutionBTest : Y9999D01Spec<SolutionB>()

abstract class Y9999D01Spec<T : Solution> : AdventSpec<T>({
    val exampleInput = "1,2,3,4"
    partOne { exampleInput shouldOutput 4 }
    partTwo { exampleInput shouldOutput 24 }
})
Workflow Example

For a complete workflow example, check out the project template.

👥 Contributing

If you'd like to help out:

  • Report bugs and submit feature requests via an issue.
  • If this helped you unlock some stars, consider giving one to this repo! ⭐

⚖ License

This project is licensed under the MIT License - see the LICENSE for details.
Advent of Code is a registered trademark of Eric K Wastl in the United States.

About

A simple library that makes running and testing your Kotlin solutions to Advent of Code puzzles a breeze.

Topics

Resources

License

Stars

Watchers

Forks

Languages