1
1
import { Then , Given , After } from '@cucumber/cucumber' ;
2
2
import assert from 'assert' ;
3
- import { execShellCommand } from 'common/utils' ;
4
3
import Zenko from 'world/Zenko' ;
4
+ import { execInCluster } from './utils/kubernetes' ;
5
+ import { Utils } from 'cli-testing' ;
5
6
6
- async function cleanDmfVolume ( ) {
7
- await execShellCommand ( 'rm -rf /cold-data/*' ) ;
7
+ /**
8
+ * Clean up S3 alias files for this specific bucket
9
+ * @param world - The Zenko world object
10
+ * @param bucketName - The name of the bucket to clean up
11
+ * @returns void
12
+ */
13
+ async function cleanDmfVolumeForBucket ( world : Zenko , bucketName : string ) {
14
+ if ( ! bucketName ) {
15
+ return ;
16
+ }
17
+
18
+ const commands = [
19
+ `find /cold-data/data/s3-aliases -name "${ bucketName } -*" -type f -delete 2>/dev/null || true` ,
20
+ `find /cold-data/data/s3-aliases -name "${ bucketName } -*" -type d -empty -delete 2>/dev/null || true`
21
+ ] ;
22
+
23
+ for ( const command of commands ) {
24
+ await execInCluster ( world , command ) ;
25
+ }
8
26
}
9
27
28
+ /**
29
+ * Check if the DMF volume contains the expected number of objects.
30
+ * This requires sorbet mock backend with UseS3Naming=true.
31
+ * Files are stored as: /cold-data/data/s3-aliases/{bucket}-{key}-{versionId}/content
32
+ * This enables parallel test execution by providing bucket-level isolation
33
+ * @param this - The Zenko world object
34
+ * @param objectCount - The expected number of objects
35
+ * @returns void
36
+ */
10
37
Then ( 'dmf volume should contain {int} objects' ,
11
- { timeout : 2 * 60 * 1000 } , async ( objectCount : number ) => {
38
+ { timeout : 2 * 60 * 1000 } , async function ( this : Zenko , objectCount : number ) {
39
+ const bucketName = this . getSaved < string > ( 'bucketName' ) ;
40
+ if ( ! bucketName ) {
41
+ throw new Error ( 'bucketName not found in test context. Ensure bucket is created before this step.' ) ;
42
+ }
43
+
12
44
let conditionOk = false ;
13
- while ( ! conditionOk ) {
14
- // Getting the number of objects inside the volume used
15
- // by the mock dmf to store transitioned objects
16
- const outStr = await execShellCommand ( 'find /cold-data -type f | wc -l' ) ;
17
- // we store two files per object (content and manifest.json)
18
- conditionOk = Number ( outStr ) === objectCount * 2 ;
45
+ let attempts = 0 ;
46
+ const maxAttempts = 60 ;
47
+
48
+ while ( ! conditionOk && attempts < maxAttempts ) {
49
+ try {
50
+ const outStr = await execInCluster (
51
+ this ,
52
+ `find /cold-data/data/s3-aliases -name "${ bucketName } -*" -type f | wc -l`
53
+ ) ;
54
+ const fileCount = Number ( outStr . trim ( ) ) ;
55
+
56
+ // We expect 2 files per object (content + manifest.json)
57
+ const expectedFileCount = objectCount * 2 ;
58
+ conditionOk = fileCount === expectedFileCount ;
59
+
60
+ if ( ! conditionOk ) {
61
+ this . logger . debug ( `DMF volume check for bucket ${ bucketName } ` , {
62
+ expected : expectedFileCount ,
63
+ found : fileCount ,
64
+ attempt : attempts + 1 ,
65
+ maxAttempts
66
+ } ) ;
67
+
68
+ if ( attempts % 10 === 0 ) {
69
+ const filesFound = await execInCluster (
70
+ this ,
71
+ `find /cold-data/data/s3-aliases -name "${ bucketName } -*" -type f 2>/dev/null`
72
+ ) ;
73
+ this . logger . debug ( `Files found for bucket ${ bucketName } :` , { files : filesFound } ) ;
74
+ }
75
+
76
+ await Utils . sleep ( 2000 ) ;
77
+ attempts ++ ;
78
+ }
79
+ } catch ( error ) {
80
+ this . logger . error ( 'Error checking DMF volume' , { error, bucket : bucketName } ) ;
81
+ throw error ;
82
+ }
83
+ }
84
+
85
+ if ( ! conditionOk ) {
86
+ const finalCount = await execInCluster (
87
+ this ,
88
+ `find /cold-data/data/s3-aliases -name "${ bucketName } -*" -type f | wc -l`
89
+ ) ;
90
+ const actualFiles = await execInCluster (
91
+ this ,
92
+ `find /cold-data/data/s3-aliases -name "${ bucketName } -*" -type f 2>/dev/null`
93
+ ) ;
94
+
95
+ assert . fail (
96
+ `DMF volume should contain ${ objectCount * 2 } files for bucket ${ bucketName } , ` +
97
+ `but found ${ finalCount . trim ( ) } after ${ attempts } attempts. ` +
98
+ `Files found: ${ actualFiles } `
99
+ ) ;
19
100
}
20
- assert ( conditionOk ) ;
101
+
102
+ this . logger . debug ( `DMF volume check passed for bucket ${ bucketName } ` , {
103
+ expectedObjects : objectCount ,
104
+ foundFiles : objectCount * 2 ,
105
+ attempts,
106
+ maxAttempts,
107
+ } ) ;
21
108
} ) ;
22
109
23
110
Given ( 'a flaky backend that will require {int} retries for {string}' ,
@@ -29,6 +116,17 @@ Given('a flaky backend that will require {int} retries for {string}',
29
116
this . addToSaved ( 'backendFlakiness' , op ) ;
30
117
} ) ;
31
118
32
- After ( { tags : '@Dmf' } , async ( ) => {
33
- await cleanDmfVolume ( ) ;
119
+ After ( { tags : '@Dmf' } , async function ( this : Zenko , results ) {
120
+ const bucketName = this . getSaved < string > ( 'bucketName' ) ;
121
+
122
+ if ( results . result ?. status === 'FAILED' ) {
123
+ this . logger . warn ( 'DMF volume was not cleaned for failed test' , {
124
+ bucket : bucketName ,
125
+ reason : 'test failed - keeping files for debugging'
126
+ } ) ;
127
+ return ;
128
+ }
129
+
130
+ await cleanDmfVolumeForBucket ( this , bucketName ) ;
131
+ this . logger . debug ( `Cleaned DMF volume for bucket: ${ bucketName } ` ) ;
34
132
} ) ;
0 commit comments