Skip to content

Commit 1c24a01

Browse files
committed
Fix Go duration parsing in kafkaCleaner test
The KafkaCleanerInterval parameter is a Go duration string (e.g. "1m") read from the Zenko CR spec.kafkaCleaner.interval. The test used parseInt("1m") to parse it, which silently returned 1 instead of 60, since parseInt stops at the first non-numeric character. This made the test timeout after ~60s (1 * 6000ms * 10) instead of ~600s (60 * 6000ms * 10), giving the kafkacleaner only one cleaning cycle to process all topics instead of ten. Under normal conditions one cycle was often enough, but when operator reconciliation caused topic recreation during the run, the kafkacleaner needed several cycles to catch up, causing the test to fail with: "Kafka cleaner did not clean the topics within the expected time" Replace parseInt with a proper Go duration parser that handles compound durations (e.g. "2h45m"), fractional values, and all standard Go time units (ns, us, ms, s, m, h). Issue: ZENKO-5218
1 parent 8bb43a7 commit 1c24a01

3 files changed

Lines changed: 62 additions & 2 deletions

File tree

tests/ctst/common/common.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ListObjectVersionsOutput } from '@aws-sdk/client-s3';
22
import { Given, setDefaultTimeout, Then, When } from '@cucumber/cucumber';
33
import { CacheHelper, Constants, Identity, IdentityEnum, S3, Utils } from 'cli-testing';
44
import Zenko from 'world/Zenko';
5-
import { safeJsonParse } from './utils';
5+
import { parseGoDuration, safeJsonParse } from './utils';
66
import assert from 'assert';
77
import { Admin, Kafka } from 'kafkajs';
88
import {
@@ -300,7 +300,7 @@ Then('i {string} be able to add user metadata to object {string}',
300300

301301
Then('kafka consumed messages should not take too much place on disk', { timeout: -1 },
302302
async function (this: Zenko) {
303-
const kfkcIntervalSeconds = parseInt(this.parameters.KafkaCleanerInterval);
303+
const kfkcIntervalSeconds = parseGoDuration(this.parameters.KafkaCleanerInterval);
304304
const checkInterval = kfkcIntervalSeconds * (1000 + 5000);
305305

306306
const timeoutID = setTimeout(() => {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import assert from 'assert';
2+
import { parseGoDuration } from './utils';
3+
4+
const cases: [string, number][] = [
5+
['1m', 60],
6+
['2h', 7200],
7+
['30s', 30],
8+
['2h45m', 9900],
9+
['500ms', 0.5],
10+
['1.5s', 1.5],
11+
['1h30m10s', 5410],
12+
['100ns', 1e-7],
13+
['10us', 1e-5],
14+
['10µs', 1e-5],
15+
['0s', 0],
16+
];
17+
18+
for (const [input, expected] of cases) {
19+
const result = parseGoDuration(input);
20+
assert.strictEqual(
21+
Math.abs(result - expected) < 1e-12, true,
22+
`parseGoDuration("${input}") = ${result}, expected ${expected}`,
23+
);
24+
}
25+
26+
const invalid = ['', 'abc', '1x', '5h 3m', '1', 'm', ' 1m'];
27+
for (const input of invalid) {
28+
assert.throws(
29+
() => parseGoDuration(input),
30+
{ message: /Invalid duration/ },
31+
`parseGoDuration("${input}") should throw`,
32+
);
33+
}
34+

tests/ctst/common/utils.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,32 @@ export const s3FunctionExtraParams: { [key: string]: Record<string, unknown>[] }
100100
}],
101101
};
102102

103+
/**
104+
* Parses a duration string in Go's time.ParseDuration format
105+
* (https://pkg.go.dev/time#ParseDuration) and returns the equivalent in seconds.
106+
* @param {string} duration - the duration string to parse (e.g. "1h30m", "500ms")
107+
* @return {number} - the duration in seconds
108+
*/
109+
export function parseGoDuration(duration: string): number {
110+
const units: Record<string, number> = {
111+
ns: 1e-9, us: 1e-6, µs: 1e-6, ms: 1e-3, s: 1, m: 60, h: 3600,
112+
};
113+
let remaining = duration;
114+
if (remaining.length === 0) {
115+
throw new Error(`Invalid duration: "${duration}"`);
116+
}
117+
let totalSeconds = 0;
118+
while (remaining.length > 0) {
119+
const match = remaining.match(/^(\d+(?:\.\d*)?)(ns|us|µs|ms|s|m|h)/);
120+
if (!match) {
121+
throw new Error(`Invalid duration: "${duration}" (unparsed: "${remaining}")`);
122+
}
123+
totalSeconds += parseFloat(match[1]) * units[match[2]];
124+
remaining = remaining.slice(match[0].length);
125+
}
126+
return totalSeconds;
127+
}
128+
103129
export function safeJsonParse<T>(jsonString: string): { ok: boolean, result: T | null, error?: Error | null } {
104130
let result: T;
105131
try {

0 commit comments

Comments
 (0)