Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Normalize all text files to LF in the repository. Working-tree endings are
# still the checkout platform's business; the stored blob is always LF.
* text=auto

# The Unix start script must stay LF even on a Windows checkout, and the Windows
# script must be CRLF or cmd.exe mis-parses labels and multi-line set blocks.
/gradlew text eol=lf
*.bat text eol=crlf

# Never run end-of-line conversion over binaries.
*.jar binary
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,20 @@ grouped by date and milestone rather than semantic version. The content tracks
the [readingbat-core](https://github.com/readingbat/readingbat-core) platform, so
many entries reflect dependency and toolchain upgrades.

## [Unreleased]
## [1.1.0] - 2026-09-21

Test-reliability and dependency upkeep. No challenge content changed.

### Changed
- Bumped `version` in `gradle.properties` to `1.1.0`.

- Upgraded dependencies: readingbat-core `3.3.1` → `3.4.0`, Ktor `3.5.1` → `3.6.0`,
Kotest `6.2.3` → `6.2.5`, kotlinter `5.6.0` → `5.7.0`, detekt `2.0.0-alpha.5` →
`2.0.0-alpha.6`, and the Gradle versions plugin `0.57.0` → `0.64.0`. Kotlin stays at
`2.4.10`, common-utils at `3.2.2`, and the JVM toolchain at **25**.

- Upgraded the Gradle wrapper `9.6.1` → `9.7.1`.

- Split the `Test all challenges` case in `ContentTests` into per-language `Test all Java
challenges` and `Test all Kotlin challenges`. `testApplication` wraps `runTest`, whose
default timeout is 60s, and verifying every challenge in one body ran close enough to
Expand All @@ -28,6 +39,15 @@ many entries reflect dependency and toolchain upgrades.
- `Per-language tests cover every challenge` guard, so adding a language to `Content.kt`
fails the suite rather than silently leaving its challenges untested.

- `.gitattributes`, which the repository had never had. `* text=auto` normalizes stored
blobs to LF regardless of a contributor's `core.autocrlf`, `*.jar` is pinned `binary`,
and the Gradle wrapper scripts get explicit endings: `/gradlew` stays `eol=lf` and
`*.bat` becomes `eol=crlf`. `gradlew.bat` had been committed with LF endings, which
`cmd.exe` mis-parses around labels and multi-line `set` blocks — CI is Linux-only, so
that would only ever have surfaced on a contributor's Windows checkout. `.editorconfig`
already declared `end_of_line = lf`, but that governs editors, not Git.
`git add --renormalize .` was a no-op, so no existing blob changed.

## [1.0.1] - 2026-08-01

Documentation-only release. No content, dependency, or build-logic changes.
Expand Down
20 changes: 19 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ This is a **ReadingBat content repository** — it defines Java and Kotlin progr
## Conventions

Record notable changes in `CHANGELOG.md` under an `[Unreleased]` heading; it becomes the
next version's section at release time. Releases are tagged without a `v` prefix (`1.0.1`),
next version's section at release time. Releases are tagged without a `v` prefix (`1.1.0`),
and the version lives in `gradle.properties`.

`.gitattributes` governs line endings: stored blobs are LF, `gradlew` stays LF, and
`*.bat` is checked out CRLF because `cmd.exe` mis-parses LF batch files.

## Build & Test Commands

`make help` lists every build, test, lint, and run target.
Expand All @@ -18,6 +21,21 @@ and the version lives in `gradle.properties`.
./gradlew test -Dkotest.filter.tests="<name>" # Filter Kotest cases by name
```

## Testing

`src/test/kotlin/ContentTests.kt` sweeps every challenge through a Ktor test host, one
language at a time. Two constraints there are easy to break:

- Await `runTestApplication` directly in the sweeps rather than calling `testApplication`.
`testApplication` is `runTestWithRealTime { runTestApplication(..) }`, and that wrapper
imposes `runTest`'s 60s default — a ceiling a Kotest `TestConfig(timeout = ..)` cannot
raise, which surfaces as `UncompletedCoroutinesError` on a slow runner instead of an
assertion failure. A Kotest test body is already a coroutine, so awaiting the inner
entry point leaves the declared timeout as the only governing limit.
- The sweeps name `content.java` and `content.kotlin` explicitly, so a language added to
`Content.kt` needs a sweep of its own. The `Per-language tests cover every challenge`
case fails the suite as a reminder.

## Architecture

### Content Definition (DSL)
Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ src/main/kotlin/<package>/*.kt # Kotlin challenge files
src/main/resources/application.conf # HOCON server config (content, site, Ktor settings)
src/test/kotlin/ContentTests.kt # Kotest suite validating every challenge
gradle/libs.versions.toml # Centralized dependency & toolchain versions
.gitattributes # Line-ending normalization (LF in-repo, CRLF for *.bat)
```

## Tech Stack
Expand All @@ -107,10 +108,19 @@ gradle/libs.versions.toml # Centralized dependency & toolchain versions

## Testing

`ContentTests.kt` uses Kotest `StringSpec` with Ktor's `testApplication` to validate
every challenge: empty answers must report `NOT_ANSWERED`, wrong answers `INCORRECT`,
and the expected outputs `CORRECT`. A CI workflow runs the tests and lint on every
push and pull request.
`ContentTests.kt` uses Kotest `StringSpec` to validate every challenge against a Ktor
test host: empty answers must report `NOT_ANSWERED`, wrong answers `INCORRECT`, and the
expected outputs `CORRECT`.

The catalog is swept one language at a time — `Test all Java challenges` and `Test all
Kotlin challenges` — sharing a `verifyAllChallenges` helper, so a failure names the
language that broke. Both sweeps declare an explicit 5-minute timeout and await
`runTestApplication` directly; `testApplication` wraps it in `runTest`, whose 60-second
default a Kotest timeout cannot raise. Because those two cases name `content.java` and
`content.kotlin` explicitly, a `Per-language tests cover every challenge` guard fails the
suite if a language added to `Content.kt` is left uncovered.

A CI workflow runs the tests and lint on JDK 25 for every push and pull request.

## Versioning

Expand Down
28 changes: 28 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,34 @@ changes, see [CHANGELOG.md](CHANGELOG.md).

---

## v1.1.0 (September 2026)

A test-reliability release, prompted by a CI failure on a pull request that contained no
code at all. `ContentTests` verified the entire catalog in a single `Test all challenges`
body, and on a slow runner that sweep drifted past a timeout nobody had set deliberately.
Ktor's `testApplication` is `runTestWithRealTime { runTestApplication(..) }`, and the
wrapper imposes `runTest`'s 60-second default — a ceiling a Kotest timeout cannot raise,
so the build failed with `UncompletedCoroutinesError` rather than an assertion.

The fix addresses both halves of the problem. The sweep is now split per language, so the
largest single body (Java, 11.4s locally) takes a little over half the time the combined
one did, and a failure names the language that broke rather than just "all challenges".
Both sweeps then await `runTestApplication` directly, which drops the hidden ceiling and
leaves an explicit 5-minute timeout as the only governing limit — confirmed by setting it
to one second and watching Kotest's own spec-scope timeout replace the coroutine error.
Because the two cases name `content.java` and `content.kotlin` explicitly, a new
`Per-language tests cover every challenge` guard fails the suite if a language added to
`Content.kt` would otherwise go silently untested.

The release also adds the repository's first `.gitattributes`. `gradlew.bat` had been
stored with LF endings, which `cmd.exe` mis-parses around labels and multi-line `set`
blocks — a latent break that Linux-only CI would never have surfaced. Stored blobs are
now normalized to LF, `*.bat` is checked out CRLF, and `*.jar` is pinned binary.

Dependencies moved forward as well: readingbat-core 3.4.0, Ktor 3.6.0, Kotest 6.2.5,
kotlinter 5.7.0, detekt 2.0.0-alpha.6, and the Gradle wrapper 9.7.1. No challenge content
changed in this release.

## v1.0.1 (August 2026)

A documentation correctness pass, with no content or build changes. Tagging `1.0.0`
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group=com.readingbat
version=1.0.1
version=1.1.0

kotlin.code.style=official
org.gradle.caching=true
Expand Down
14 changes: 7 additions & 7 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[versions]
detekt = "2.0.0-alpha.5"
gradle-wrapper = "9.6.1"
detekt = "2.0.0-alpha.6"
gradle-wrapper = "9.7.1"
jvm = "25"
kotest = "6.2.3"
kotest = "6.2.5"
kotlin = "2.4.10"
kotlinter = "5.6.0"
ktor = "3.5.1"
kotlinter = "5.7.0"
ktor = "3.6.0"
logging = "8.0.4"
readingbat = "3.3.1"
readingbat = "3.4.0"
utils = "3.2.2"
versions = "0.57.0"
versions = "0.64.0"

[libraries]
core-utils = { module = "com.pambrose.common-utils:core-utils", version.ref = "utils" }
Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
Expand Down
11 changes: 10 additions & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ This repository provides interactive coding challenges served by [readingbat-cor
- `src/main/kotlin/ContentServer.kt`: Server entry point (delegates to readingbat-core)
- `src/main/java/<package>/*.java`: Java challenge files (each a class with a static method and main())
- `src/main/kotlin/<package>/*.kt`: Kotlin challenge files (top-level functions with main())
- `src/test/kotlin/ContentTests.kt`: Kotest tests validating all challenges via Ktor test server
- `src/test/kotlin/ContentTests.kt`: Kotest suite validating all challenges via a Ktor test host
- `.gitattributes`: line-ending normalization — LF in the repository, `*.bat` checked out CRLF, `*.jar` binary

## Challenge Convention

Expand All @@ -37,6 +38,14 @@ Run `make help` for the authoritative target list.
- `make versions` — check for dependency updates
- `./gradlew test -Dkotest.filter.tests="<name>"` — filter Kotest cases by name

## Testing

`ContentTests.kt` sweeps the catalog one language at a time (`Test all Java challenges`, `Test all Kotlin challenges`) through a shared `verifyAllChallenges` helper: empty answers must report `NOT_ANSWERED`, wrong answers `INCORRECT`, and each challenge's `main()` output `CORRECT`.

Two constraints are easy to break when editing the suite:
- The sweeps await `runTestApplication` directly rather than calling `testApplication`. `testApplication` is `runTestWithRealTime { runTestApplication(..) }`, and that wrapper imposes `runTest`'s 60s default — a ceiling a Kotest `TestConfig(timeout = ..)` cannot raise. Awaiting the inner entry point leaves the declared 5-minute timeout as the only limit.
- The sweeps name `content.java` and `content.kotlin` explicitly, so a new language in `Content.kt` needs its own sweep. The `Per-language tests cover every challenge` case fails the suite as a reminder.

## Versioning

Releases are tagged (`1.0.0` onward) with the version tracked in `gradle.properties`. See `CHANGELOG.md` for the dated history and `RELEASE_NOTES.md` for narrative milestones.
Expand Down
Loading