Skip to content
Draft
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
20 changes: 20 additions & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ permissions:
contents: read

jobs:
offline-gate:
name: Offline gate (Windows, JDK 21)
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up JDK
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: temurin
java-version: 21
cache: maven
- name: Prime the exact Maven test runtime
run: mvn -B -ntp "-Dtest=quality.SmokeMethodTestHarnessTest" test --file pom.xml
- name: Run the strict offline test gate
shell: pwsh
run: .\scripts\run-offline-gate.ps1 -Goal test -StrictDependencies

category-tests:
name: Test (${{ matrix.category }})
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- `IntegrationSuite` – stateful transaction validation behavior.
- `PropertySuite` – jqwik property/invariant tests.
- `.github` contains CI, templates, and dependency automation.
- `scripts/run-offline-gate.ps1` provides a network-free local Maven quality gate.

## Design principles used in this repo

Expand All @@ -33,5 +34,5 @@
## Future quality directions

- Increase property coverage into additional stable, pure utilities where invariant checks are natural.
- Add explicit domain-level integration tests for edge-case transaction flows (duplicates / ordering / insufficient balance).
- Expand domain-level integration coverage when new transaction rules are introduced.
- Keep production methods with concise intent comments for non-obvious logic.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## Unreleased

- Corrected boundary, overflow, validation, determinism and complexity defects across kata utilities.
- Hardened transaction validation and made returned status collections immutable.
- Replaced unbounded iteration in encryption and range-counting helpers with logarithmic algorithms.
- Made the generated smoke harness fail when every executable API path fails.
- Added regression coverage for scheduler extremes, queueing, Fibonacci, trailing zeros and other edge cases.
- Added a PowerShell offline quality gate with strict dependency-cache mode and a documented cached-JUnit fallback.
- Added a Windows CI job that primes dependencies and then exercises the strict offline gate.
- Expanded the verified test baseline to 594 tests.

## 1.1 - public quality hardening (2026-06-09)

- Added CI, static analysis, and dependency management updates for repository quality.
Expand Down
10 changes: 10 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ This repository is a portfolio-grade Java kata workspace focused on deterministi
## CI and Quality Signals

- `.github/workflows/maven.yml` runs category jobs on JDK 21 and full `mvn verify` on JDK 17 and 21.
- The same workflow validates the strict offline PowerShell gate on Windows after an explicit dependency-prime step.
- `.github/workflows/quality.yml` runs Checkstyle, PMD, and SpotBugs without executing tests.
- `.github/workflows/codeql-analysis.yml` runs security analysis.
- `.github/dependabot.yml` keeps dependency automation visible.
Expand All @@ -58,6 +59,15 @@ mvn -B test -Dtest='quality.IntegrationSuite'
mvn -B test -Dtest='quality.PropertySuite'
```

Run the complete PowerShell gate without network access:

```powershell
.\scripts\run-offline-gate.ps1
```

See [`TESTING.md`](TESTING.md) for strict dependency-cache mode and the documented
cached-JUnit fallback.

## Reviewer Checklist

- No hidden mutation of test inputs inside algorithmic methods.
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
- **Coverage-gate**: JaCoCo ломает `mvn verify`, если покрытие ниже 70% строк, ветвлений
или инструкций.
- **Отдельный quality-гейт**: workflow `.github/workflows/quality.yml` для чистых статических проверок без прогона тестов.
- **Офлайн-гейт**: Windows CI после явного заполнения кэша запускает PowerShell-проверку в строгом режиме без сети.
- **Checkstyle / PMD / SpotBugs**: статические проверки заведены в quality-гейт.
- **Checkstyle**: проектный набор правил в `config/checkstyle/checkstyle.xml` и явные suppressions для generated smoke-тестов.
- **SpotBugs**: чистый отчёт с явными исключениями совместимости в `config/spotbugs-exclude.xml`.
Expand All @@ -63,6 +64,15 @@ mvn -B test -Dtest='quality.IntegrationSuite'
mvn -B test -Dtest='quality.PropertySuite'
```

Полный локальный прогон без сети на PowerShell:

```powershell
.\scripts\run-offline-gate.ps1
```

Подробности о строгом режиме зависимостей и fallback на уже закэшированную версию JUnit
описаны в [`TESTING.md`](TESTING.md).

### Что отражает репозиторий как "публично привлекательный"

- Есть CI с понятными этапами и артефактами (`surefire`, `jacoco`).
Expand Down
20 changes: 18 additions & 2 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,36 @@ mvn -B test -Dtest='quality.PropertySuite'
- line coverage: 70%
- branch coverage: 70%
- instruction coverage: 70%
- Current verified local baseline: 455 tests, 91.4% line coverage,
85.1% branch coverage and 92.7% instruction coverage.
- Current verified local baseline: 594 tests. Coverage is enforced by the ratios above;
the generated HTML/XML report under `target/site/jacoco` is the source of exact values.
- Run:

```bash
mvn -B verify
```

### Offline gate (PowerShell)

The repository also provides a network-free local gate:

```powershell
.\scripts\run-offline-gate.ps1
.\scripts\run-offline-gate.ps1 -Goal test
.\scripts\run-offline-gate.ps1 -StrictDependencies
```

The script always passes Maven `--offline`. By default, when the exact JUnit BOM from
`pom.xml` is not present locally, it warns and uses the newest cached release from the
same major line for validation. `-StrictDependencies` disables that fallback and fails
unless the exact dependency set is already cached.

- CI uploads `target/surefire-reports` and `target/site/jacoco`.

## 6) CI matrix

- Category workflow: smoke/integration/property tags (JDK 21)
- Full verify workflow: JDK 17 and 21
- Strict offline PowerShell gate: Windows, JDK 21 (after an explicit cache-prime step)
- Static workflow: static checks only (JDK 21)

## 7) UI category status
Expand Down
75 changes: 75 additions & 0 deletions scripts/run-offline-gate.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
[CmdletBinding()]
param(
[ValidateSet('test', 'verify')]
[string] $Goal = 'verify',

[switch] $StrictDependencies
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$repoRoot = Split-Path -Parent $PSScriptRoot
$pomPath = Join-Path $repoRoot 'pom.xml'
$maven = Get-Command mvn -ErrorAction Stop
$localRepository = if ($env:MAVEN_REPO_LOCAL) {
$env:MAVEN_REPO_LOCAL
} else {
Join-Path $env:USERPROFILE '.m2\repository'
}

[xml] $pom = Get-Content -LiteralPath $pomPath -Raw
$configuredJUnit = [string] $pom.project.properties.'junit.version'
$configuredPlatform = [string] $pom.project.properties.'junit.platform.version'
$configuredBom = Join-Path $localRepository (
'org\junit\junit-bom\{0}\junit-bom-{0}.pom' -f $configuredJUnit
)

$mavenArguments = @(
'--offline'
'--batch-mode'
'--no-transfer-progress'
'--file'
$pomPath
)

if (-not (Test-Path -LiteralPath $configuredBom)) {
if ($StrictDependencies) {
throw "The configured JUnit BOM $configuredJUnit is absent from the local Maven cache."
}

$configuredMajor = ([version] $configuredJUnit).Major
$bomRoot = Join-Path $localRepository 'org\junit\junit-bom'
$fallback = Get-ChildItem -LiteralPath $bomRoot -Directory -ErrorAction SilentlyContinue |
Where-Object {
$_.Name -match '^\d+\.\d+\.\d+$' -and
([version] $_.Name).Major -eq $configuredMajor -and
(Test-Path -LiteralPath (Join-Path $_.FullName ("junit-bom-{0}.pom" -f $_.Name)))
} |
Sort-Object { [version] $_.Name } -Descending |
Select-Object -First 1

if ($null -eq $fallback) {
throw "No JUnit $configuredMajor.x BOM is available in the local Maven cache."
}

Write-Warning ((
"JUnit BOM $configuredJUnit is not cached; offline validation uses cached {0}. " +
"Run with -StrictDependencies to require the exact POM version."
) -f $fallback.Name)
$mavenArguments += "-Djunit.version=$($fallback.Name)"

if ($configuredPlatform -eq $configuredJUnit) {
$mavenArguments += "-Djunit.platform.version=$($fallback.Name)"
}
}

Push-Location $repoRoot
try {
& $maven.Source @mavenArguments $Goal
if ($LASTEXITCODE -ne 0) {
throw "Offline Maven gate failed with exit code $LASTEXITCODE."
}
} finally {
Pop-Location
}
15 changes: 14 additions & 1 deletion src/main/java/interview/CallbackScheduler.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,23 @@ public CallbackScheduler() {
public void schedule(Runnable callback, Instant when) {
Objects.requireNonNull(callback);
Objects.requireNonNull(when);
long delayMillis = Math.max(0L, Duration.between(Instant.now(), when).toMillis());
long delayMillis = delayMillisUntil(when);
scheduler.schedule(callback, delayMillis, TimeUnit.MILLISECONDS);
}

private static long delayMillisUntil(Instant when) {
Duration delay = Duration.between(Instant.now(), when);
if (delay.isNegative() || delay.isZero()) {
return 0L;
}
try {
return delay.toMillis();
} catch (ArithmeticException ignored) {
// Instant supports a wider range than a millisecond delay stored in a long.
return Long.MAX_VALUE;
}
}

@Override
public void close() {
scheduler.shutdownNow();
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/kyu4/MorseCodeDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringJoiner;

Expand Down Expand Up @@ -129,7 +130,7 @@ public static String decodeMorse(String morseCode) {
}
decodedWords.add(decodedWord.toString());
}
return String.join(" ", decodedWords).toUpperCase();
return String.join(" ", decodedWords).toUpperCase(Locale.ROOT);
}

public static String encode(String toMorseCode) {
Expand All @@ -141,7 +142,7 @@ public static String encode(String toMorseCode) {
for (String word : toMorseCode.trim().split("\\s+")) {
StringJoiner letters = new StringJoiner(" ");
for (char letter : word.toCharArray()) {
String morse = ALPHABET_TO_MORSE.get(String.valueOf(letter).toLowerCase());
String morse = ALPHABET_TO_MORSE.get(String.valueOf(letter).toLowerCase(Locale.ROOT));
if (morse == null) {
throw new IllegalArgumentException("Unsupported Morse character: " + letter);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/kyu5/HumanReadableTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ public class HumanReadableTime {
private static final int SIXTY = 60;

public static String makeReadable(int seconds) {
if (seconds > 359999) {
throw new IllegalArgumentException("very long arguments");
if (seconds < 0 || seconds > 359999) {
throw new IllegalArgumentException("seconds must be between 0 and 359999");
}
int HH = 0;
int MM = 0;
Expand Down
29 changes: 8 additions & 21 deletions src/main/java/kyu5/MaximumSubarraySum.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,21 @@
package kyu5;


import java.util.Arrays;


public class MaximumSubarraySum {

//5

public static int sequence(int[] arr) {
int maximumSubArraySum = arraySum(arr);
for (int i = 0; i <= arr.length; i++) {
for (int j = arr.length; j >= i; j--) {
int[] ints = Arrays.copyOfRange(arr, i, j);
int subArraySum = arraySum(ints);
if (subArraySum > maximumSubArraySum) {
maximumSubArraySum = subArraySum;
}
}
if (arr == null) {
throw new IllegalArgumentException("array must not be null");
}
return maximumSubArraySum;
}

private static int arraySum(int[] arr) {
int result = 0;
for (int i : arr
) {
result += i;
long best = 0L;
long current = 0L;
for (int value : arr) {
current = Math.max(0L, current + value);
best = Math.max(best, current);
}
return result;
return Math.toIntExact(best);
}

/**
Expand Down
29 changes: 10 additions & 19 deletions src/main/java/kyu5/NumberOfTrailingZerosOfN.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
package kyu5;


import java.math.BigInteger;


public class NumberOfTrailingZerosOfN {

//5 https://www.codewars.com/kata/52f787eb172a8b4ae1000a34/train/java

public static int zeros(int n) {
return zeroOfTrailing(factorial(n));
}

private static int zeroOfTrailing(Long l) {
int count = 0;
while (l % 10 == 0) {
count++;
l /= 10;
if (n < 0) {
throw new IllegalArgumentException("n must not be negative");
}
return count;
}

private static long factorial(int n) {
BigInteger result = BigInteger.ONE;
for (int i = 1; i <= n; i++) {
result = result.multiply(BigInteger.valueOf(i));
int zeroCount = 0;
for (int factor = 5; factor <= n; ) {
zeroCount += n / factor;
if (factor > n / 5) {
break;
}
factor *= 5;
}
return result.longValue();
return zeroCount;
}

}
Loading