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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ jobs:
- name: Run performance regression checks
run: make performance-check

- name: Run runtime stress checks
run: make stress-check

- name: Build debug APK
run: make assemble

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Added

- Real-engine performance regression tests covering QuickJS runtime reuse and tolerant cold-start, sentence and paragraph timing budgets.
- Runtime stress coverage for prolonged Grammalecte analysis, concurrent engine calls, repeated QuickJS lifecycle cycles and retained QuickJS memory growth.
- Initial modular Android project foundation.
- Pure Kotlin `GrammarEngine` abstraction for correction results.
- Native Android `SpellCheckerService` integration.
Expand Down
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
GRADLE := ./gradlew

.PHONY: bootstrap vendor check performance-check test lint assemble instrumented refresh-verification-metadata clean
.PHONY: bootstrap vendor check performance-check stress-check test lint assemble instrumented refresh-verification-metadata clean

bootstrap:
./tools/bootstrap-gradle-wrapper.sh
Expand All @@ -22,6 +22,13 @@ performance-check:
-PgrammalectePerf=1 \
--rerun-tasks

stress-check:
$(GRADLE) --no-daemon \
:engine-grammalecte:testDebugUnitTest \
--tests '*EngineRuntimeStressTest*' \
-PgrammalecteStress=1 \
--rerun-tasks

test:
$(GRADLE) --no-daemon :core:test :engine-grammalecte:test :spellchecker:test

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Exit criterion: correction remains usable in common editable applications even w
- [x] Review all user-facing French strings
- [x] Add compatibility documentation
- [x] Add performance regression tests
- [ ] Add memory/runtime stress tests
- [x] Add memory/runtime stress tests

## Phase 5 — Public distribution

Expand Down
39 changes: 39 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,42 @@ profiling concern.
Performance regression tests are intended to detect substantial regressions,
such as accidental runtime reinitialization, repeated asset loading or major
analysis slowdowns, rather than small timing variations.

## Runtime and memory stress tests

`EngineRuntimeStressTest` exercises the real embedded QuickJS/Grammalecte
runtime under sustained use.

The stress suite covers:

- a long-lived engine performing hundreds of grammar analyses and repeated
spelling checks;
- retained QuickJS memory before and after sustained analysis, measured after
explicit QuickJS garbage collection;
- concurrent bursts of grammar and spelling requests sharing one serialized
runtime;
- repeated engine creation, use and closure cycles.

Run the suite with:

make stress-check

The stress tests are opt-in through `-PgrammalecteStress=1` and are executed
by CI through the dedicated `stress-check` target.

The initial development measurement after 400 long-lived analyses reported:

- QuickJS memory before stress: 30.00 MiB;
- QuickJS memory after stress: 30.00 MiB;
- retained-memory growth: 0.00 MiB;
- QuickJS allocated memory after stress: 40.62 MiB;
- objects after stress: 91,502;
- strings after stress: 36,620.

The retained-memory regression budget is 8 MiB. This intentionally allows
normal runtime and host variation while detecting substantial accumulation
across repeated corrections.

These measurements concern the embedded QuickJS runtime used by the JVM test
environment. They protect engine lifecycle and memory behavior but are not a
claim about total Android process memory or device-specific RSS.
8 changes: 8 additions & 0 deletions engine-grammalecte/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ tasks.withType<org.gradle.api.tasks.testing.Test>().configureEach {
.orElse("0")
.get(),
)

systemProperty(
"grammalecte.stress",
providers
.gradleProperty("grammalecteStress")
.orElse("0")
.get(),
)
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import kotlinx.coroutines.runBlocking
import org.json.JSONArray
import org.json.JSONObject

internal data class GrammalecteRuntimeMemorySnapshot(
val memoryUsedBytes: Long,
val mallocBytes: Long,
val objectCount: Long,
val stringCount: Long,
)

class GrammalecteQuickJsEngine internal constructor(
private val assetLoader: AssetTextLoader,
private val rulePreferences: GrammalecteRulePreferences? = null,
Expand Down Expand Up @@ -598,6 +605,27 @@ class GrammalecteQuickJsEngine internal constructor(
}
}

internal fun collectRuntimeGarbageAndSnapshot(): GrammalecteRuntimeMemorySnapshot =
synchronized(lock) {
val js =
runtime
?: createRuntime().also {
runtime = it
}

js.gc()

val usage =
js.memoryUsage

GrammalecteRuntimeMemorySnapshot(
memoryUsedBytes = usage.memoryUsedSize,
mallocBytes = usage.mallocSize,
objectCount = usage.objCount,
stringCount = usage.strCount,
)
}

override fun close() {
synchronized(lock) {
runtime?.close()
Expand Down
Loading
Loading