[CP-3547] Script added to generate max storage load test files (#2472)

This commit is contained in:
Daniel Karski
2025-05-30 13:52:09 +02:00
committed by GitHub
parent 56abcb9399
commit 0bb1f6fc73
8 changed files with 280 additions and 18 deletions

View File

@@ -40,6 +40,7 @@
"e2e:test:cicd:standalone": "xvfb-run --auto-servernum --server-args='-screen 0 1024x768x24' nx e2e:test:cicd:standalone mudita-center-e2e",
"e2e:test:cicd:mock": "xvfb-run --auto-servernum --server-args='-screen 0 1024x768x24' nx e2e:test:cicd:mock mudita-center-e2e",
"test-file:generate-and-push": "ts-node scripts/manage-test-files/generate-and-push-test-files.ts",
"test-file:generate-max-storage-load": "ts-node scripts/manage-test-files/generate-max-storage-load.ts",
"test-file:clear": "ts-node scripts/manage-test-files/clear-test-files.ts",
"api-device-testing": "npx nx run api-devices-testing:test --skip-nx-cache"
},

View File

@@ -1,20 +1,31 @@
import { checkAdbAvailability, checkIfDeviceLocked, ensureSingleDevice, execPromise } from "./helpers"
import {
checkAdbAvailability,
checkIfDeviceLocked,
ensureSingleDevice,
execPromise,
} from "./helpers"
import { getRemotePaths } from "./get-remote-paths"
import {
fileManagerTestFilesFolderName,
lowStorageFolderName,
RemotePathsTarget,
} from "./manage-test-files.const"
import { parseTargetArg } from "./parse-target-arg"
async function clearTestFiles(): Promise<void> {
async function clearDeviceDirectory(path: string): Promise<void> {
try {
console.log("Removing test files from the device...")
const command = `adb shell rm -rf /storage/emulated/0/file-manager-test-files`
console.log(`🧹 Removing files from ${path}...`)
const command = `adb shell rm -rf "${path}"`
const { stdout, stderr } = await execPromise(command)
if (stdout) console.log(`Output: ${stdout}`)
if (stderr) console.error(`Error: ${stderr}`)
console.log("Test files successfully removed from the device.")
console.log(`✅ Files successfully removed from ${path}`)
} catch (err) {
console.error(
`Failed to remove test files from the device. Ensure the device is connected and accessible via ADB.`
`Failed to remove files from ${path}:`,
(err as Error).message
)
console.error(`Error Details: ${(err as Error).message}`)
}
}
@@ -22,7 +33,16 @@ async function main(): Promise<void> {
await checkAdbAvailability()
await ensureSingleDevice()
await checkIfDeviceLocked()
await clearTestFiles()
const target = parseTargetArg(process.argv.slice(2), RemotePathsTarget.both)
const folders = [lowStorageFolderName, fileManagerTestFilesFolderName]
for (const folder of folders) {
const paths = await getRemotePaths(target, folder)
for (const path of paths) {
await clearDeviceDirectory(path)
}
}
process.exit(0)
}

View File

@@ -1,7 +1,16 @@
import sharp from "sharp"
import fs from "fs-extra"
import path from "path"
import { checkAdbAvailability, checkIfDeviceLocked, ensureSingleDevice, pushToDevice, restartDevice } from "./helpers"
import {
checkAdbAvailability,
checkIfDeviceLocked,
ensureSingleDevice,
pushToDevice,
restartDevice,
} from "./helpers"
import { getRemotePaths } from "./get-remote-paths"
import { fileManagerTestFilesFolderName } from "./manage-test-files.const"
import { parseTargetArg } from "./parse-target-arg"
interface TestCase {
name: string
@@ -97,16 +106,30 @@ async function main(): Promise<void> {
try {
const testCases = await loadTestCases()
await checkAdbAvailability()
await ensureSingleDevice()
await checkIfDeviceLocked()
await fs.remove(outputDir)
await generateImageFiles(testCases.images)
await generateAudioFiles(testCases.audio)
await generateEBookFiles(testCases.ebooks)
await generateAPKFiles(testCases.apk)
await checkAdbAvailability()
await ensureSingleDevice()
await checkIfDeviceLocked()
await pushToDevice(outputDir)
const target = parseTargetArg(process.argv.slice(2))
const remotePaths = await getRemotePaths(
target,
fileManagerTestFilesFolderName
)
for (const remotePath of remotePaths) {
await pushToDevice(outputDir, remotePath)
}
await restartDevice()
await fs.remove(outputDir)
} catch (err) {
console.error("Error during test file generation:", (err as Error).message)
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import {
checkAdbAvailability,
checkIfDeviceLocked,
ensureSingleDevice,
execPromise,
} from "./helpers"
import { getRemotePaths } from "./get-remote-paths"
import { lowStorageFolderName } from "./manage-test-files.const"
import { parseTargetArg } from "./parse-target-arg"
const partSizeBytes = 10 * 1024 ** 3 // 10 GB
const targetFillSizeBytes = 100 * 1024 ** 3 // 100 GB
const writePart = async (
bytes: number,
remotePath: string,
index: number
): Promise<number> => {
const mb = Math.floor(bytes / (1024 * 1024))
const filename = `dummy_file_${mb}_${Date.now()}_${index}.bin`
console.log(`📦 Writing part ${index + 1}: ${mb} MB to ${remotePath}...`)
const start = Date.now()
try {
await execPromise(
`adb shell 'dd if=/dev/zero of="${remotePath}/${filename}" bs=1M count=${mb}'`
)
const duration = ((Date.now() - start) / 1000).toFixed(1)
console.log(`✅ Part ${index + 1} written successfully in ${duration}s`)
return bytes
} catch (err) {
console.error(
`❌ Failed to write part ${index + 1} — likely due to lack of space.`
)
try {
const { stdout } = await execPromise(
`adb shell ls -l "${remotePath}/${filename}"`
)
const parts = stdout.trim().split(/\s+/)
const actualSize = parseInt(parts[4], 10)
const duration = ((Date.now() - start) / 1000).toFixed(1)
console.log(
` Partial file created: ${(actualSize / 1024 ** 2).toFixed(
2
)} MB in ${duration}s`
)
return actualSize
} catch {
console.warn("⚠️ Could not determine partial file size.")
return 0
}
}
}
const simulateLowStorage = async (
totalTargetBytes: number,
remotePath: string
): Promise<void> => {
console.log(`📁 Creating directory at ${remotePath}...`)
await execPromise(`adb shell mkdir -p "${remotePath}"`)
console.log("📊 Starting low storage simulation in 10 GB chunks...")
let written = 0
let index = 0
while (written < totalTargetBytes) {
const remaining = totalTargetBytes - written
const sizeToWrite = Math.min(partSizeBytes, remaining)
const writtenNow = await writePart(sizeToWrite, remotePath, index)
if (writtenNow === 0) break
written += writtenNow
index++
}
console.log(
`🏁 Done. Total written: ${(written / 1024 ** 3).toFixed(
2
)} GB in ${index} part(s).`
)
}
async function main(): Promise<void> {
await checkAdbAvailability()
await ensureSingleDevice()
await checkIfDeviceLocked()
const target = parseTargetArg(process.argv.slice(2))
const paths = await getRemotePaths(target, lowStorageFolderName)
console.log("📱 Simulating low storage on Kompakt device...")
console.log(
" This process can take several minutes depending on device speed."
)
console.log("💡 Example: Writing 10 GB takes ~1 minute on average.")
for (const path of paths) {
await simulateLowStorage(targetFillSizeBytes, path)
}
process.exit(0)
}
void main()

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { execPromise } from "./helpers"
import { RemotePathsTarget } from "./manage-test-files.const"
async function getExternalStoragePath(folder: string): Promise<string | null> {
try {
const { stdout } = await execPromise(`adb shell ls /storage`)
const candidates = stdout
.trim()
.split("\n")
.filter((name) => /^[\w\d]{4}-[\w\d]{4}$/.test(name))
if (candidates.length === 0) {
console.warn("⚠️ No external storage detected.")
return null
}
const selected = candidates[0]
return `/storage/${selected}/DCIM/${folder}`
} catch (err) {
console.error(
"❌ Failed to detect external storage:",
(err as Error).message
)
return null
}
}
export async function getRemotePaths(
target: RemotePathsTarget,
folder: string
): Promise<string[]> {
const internalStoragePath = `/storage/emulated/0/${folder}`
const paths: string[] = []
if (
target === RemotePathsTarget.internal ||
target === RemotePathsTarget.both
) {
paths.push(internalStoragePath)
}
if (
target === RemotePathsTarget.external ||
target === RemotePathsTarget.both
) {
const externalPath = await getExternalStoragePath(folder)
if (externalPath) {
paths.push(externalPath)
} else {
console.warn("⚠️ Skipping external storage path not found.")
}
}
if (paths.length === 0) {
console.error("❌ No valid storage targets found.")
process.exit(1)
}
return paths
}

View File

@@ -68,17 +68,19 @@ export async function checkIfDeviceLocked(): Promise<void> {
}
}
export async function pushToDevice(outputDir: string): Promise<void> {
export async function pushToDevice(
outputDir: string,
remotePath: string
): Promise<void> {
try {
const command = `adb push ${outputDir} /storage/emulated/0/`
const command = `adb push ${outputDir} ${remotePath}`
const { stdout, stderr } = await execPromise(command)
if (stdout) console.log(stdout)
if (stderr) console.error(stderr)
} catch (err) {
console.error(
`Failed to push files to the device. Ensure the device is connected and accessible via ADB.`
`Failed to push files to the device: ${(err as Error).message}`
)
console.error(`Error Details: ${(err as Error).message}`)
process.exit(1)
}
}

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
export const lowStorageFolderName = "low-storage"
export const fileManagerTestFilesFolderName = "file-manager-test-files"
export enum RemotePathsTarget {
internal = "internal",
external = "external",
both = "both",
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/
import { RemotePathsTarget } from "./manage-test-files.const"
export const parseTargetArg = (
args: string[],
fallback: RemotePathsTarget = RemotePathsTarget.internal
): RemotePathsTarget => {
const targetArg = args.find((arg) => arg.startsWith("--target="))
const rawValue = targetArg?.split("=")[1]
const isValid = Object.values(RemotePathsTarget).includes(
rawValue as RemotePathsTarget
)
if (isValid) {
return rawValue as RemotePathsTarget
}
if (rawValue) {
console.warn(
`⚠️ Unknown target "${rawValue}", falling back to "${fallback}".`
)
}
return fallback
}