Remove server elements from unit tests (#2777)

This commit is contained in:
Chenlei Hu
2025-02-28 20:01:40 -05:00
committed by GitHub
parent 3e54146afd
commit a244f295a6
7 changed files with 0 additions and 261 deletions

View File

@@ -21,8 +21,5 @@ DEPLOY_COMFYUI_DIR=/home/ComfyUI/web
# If you aren't using a separate install for testing, point this to your regular install.
TEST_COMFYUI_DIR=/home/ComfyUI
# The directory containing the ComfyUI_examples repo used to extract test workflows.
EXAMPLE_REPO_PATH=tests-ui/ComfyUI_examples
# Whether to enable minification of the frontend code.
ENABLE_MINIFY=true

View File

@@ -68,27 +68,8 @@ jobs:
ComfyUI_frontend
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install requirements
run: |
python -m pip install --upgrade pip
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
pip install wait-for-it
working-directory: ComfyUI
- name: Start ComfyUI server
run: |
python main.py --cpu --multi-user &
wait-for-it --service 127.0.0.1:8188 -t 600
working-directory: ComfyUI
- name: Run Jest tests
run: |
npm run test:generate
npm run test:jest -- --verbose
working-directory: ComfyUI_frontend

View File

@@ -546,9 +546,7 @@ navigate to `http://<server_ip>:5173` (e.g. `http://192.168.2.20:5173` here), to
### Unit Test
- `git clone https://github.com/comfyanonymous/ComfyUI_examples.git` to `tests-ui/ComfyUI_examples` or the EXAMPLE_REPO_PATH location specified in .env
- `npm i` to install all dependencies
- `npm run test:generate` to fetch `tests-ui/data/object_info.json`
- `npm run test:jest` to execute all unit tests.
### Component Test

View File

@@ -20,7 +20,6 @@
"format": "prettier --write './**/*.{js,ts,tsx,vue,mts}'",
"format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}'",
"test:jest": "jest --config jest.config.ts",
"test:generate": "npx tsx tests-ui/setup",
"test:browser": "npx playwright test",
"test:component": "vitest run src/components/",
"prepare": "husky || true",

View File

@@ -1,172 +0,0 @@
// @ts-strict-ignore
/*
Script to generate test API json from the ComfyUI_examples repo.
Requires the repo to be cloned to the tests-ui directory or specified via the EXAMPLE_REPO_PATH env var.
*/
import chalk from 'chalk'
import dotenv from 'dotenv'
import fs from 'fs'
import { fileURLToPath } from 'node:url'
import path from 'path'
import { getFromFlacBuffer } from '@/scripts/metadata/flac'
import { getFromPngBuffer } from '@/scripts/metadata/png'
dotenv.config()
const dirname = path.dirname(fileURLToPath(import.meta.url))
const repoPath =
process.env.EXAMPLE_REPO_PATH || path.resolve(dirname, 'ComfyUI_examples')
const workflowsPath = path.resolve(dirname, 'workflows', 'examples')
if (!fs.existsSync(repoPath)) {
console.error(
`ComfyUI_examples repo not found. Please clone this to ${repoPath} or set the EXAMPLE_REPO_PATH env var (see .env_example) and re-run.`
)
}
if (!fs.existsSync(workflowsPath)) {
await fs.promises.mkdir(workflowsPath)
}
async function* getFiles(
dir: string,
...exts: string[]
): AsyncGenerator<string, void, void> {
const dirents = await fs.promises.readdir(dir, { withFileTypes: true })
for (const dirent of dirents) {
const res = path.resolve(dir, dirent.name)
if (dirent.isDirectory()) {
yield* getFiles(res, ...exts)
} else if (exts.includes(path.extname(res))) {
yield res
}
}
}
async function validateMetadata(metadata: Record<string, string>) {
const check = (prop: 'prompt' | 'workflow') => {
const v = metadata?.[prop]
if (!v) throw `${prop} not found in metadata`
try {
JSON.parse(v)
} catch (error) {
throw `${prop} invalid json: ${error.message}`
}
return v
}
return { prompt: check('prompt'), workflow: check('workflow') }
}
async function hasExampleChanged(
existingFilePath: string,
exampleJson: string
) {
return exampleJson !== (await fs.promises.readFile(existingFilePath, 'utf8'))
}
// Example images to ignore as they don't contain workflows
const ignore = [
'unclip_sunset.png',
'unclip_mountains.png',
'inpaint_yosemite_inpaint_example.png',
'controlnet_shark_depthmap.png',
'controlnet_pose_worship.png',
'controlnet_pose_present.png',
'controlnet_input_scribble_example.png',
'controlnet_house_scribble.png'
]
// Find all existing examples so we can check if any are removed/changed
const existing = new Set(
(await fs.promises.readdir(workflowsPath, { withFileTypes: true }))
.filter((d) => d.isFile())
.map((d) => path.resolve(workflowsPath, d.name))
)
const results = {
new: [],
changed: [],
unchanged: [],
missing: [],
failed: []
}
let total = 0
for await (const file of getFiles(repoPath, '.png', '.flac')) {
const cleanedName = path
.relative(repoPath, file)
.replaceAll('/', '_')
.replaceAll('\\', '_')
if (ignore.includes(cleanedName)) continue
total++
let metadata: { prompt: string; workflow: string }
try {
const { buffer } = await fs.promises.readFile(file)
switch (path.extname(file)) {
case '.png':
metadata = await validateMetadata(getFromPngBuffer(buffer))
break
case '.flac':
metadata = await validateMetadata(getFromFlacBuffer(buffer))
break
}
const outPath = path.resolve(workflowsPath, cleanedName + '.json')
const exampleJson = JSON.stringify(metadata)
if (existing.has(outPath)) {
existing.delete(outPath)
if (await hasExampleChanged(outPath, exampleJson)) {
results.changed.push(outPath)
} else {
// Unchanged, no point in re-saving
results.unchanged.push(outPath)
continue
}
} else {
results.new.push(outPath)
}
await fs.promises.writeFile(outPath, exampleJson, 'utf8')
} catch (error) {
results.failed.push({ file, error })
}
}
// Any workflows left in the existing set are now missing, these will want checking and manually removing
results.missing.push(...existing)
const c = (v: number, gt0: 'red' | 'yellow' | 'green') =>
chalk[v > 0 ? gt0 : 'gray'](v)
console.log(`Processed ${chalk.green(total)} examples`)
console.log(` ${chalk.gray(results.unchanged.length)} unchanged`)
console.log(` ${c(results.changed.length, 'yellow')} changed`)
console.log(` ${c(results.new.length, 'green')} new`)
console.log(` ${c(results.missing.length, 'red')} missing`)
console.log(` ${c(results.failed.length, 'red')} failed`)
if (results.missing.length) {
console.log()
console.log(
chalk.red(
'The following examples are missing and require manual reviewing & removal:'
)
)
for (const m of results.missing) {
console.log(m)
}
}
if (results.failed.length) {
console.log()
console.log(chalk.red('The following examples failed to extract:'))
for (const m of results.failed) {
console.log(m.file)
console.error(m.error)
console.log()
}
}

View File

@@ -1,46 +0,0 @@
import { existsSync, mkdirSync, writeFileSync } from 'fs'
import http from 'http'
import { resolve } from 'path'
async function setup() {
await new Promise<void>((res, rej) => {
http
.get('http://127.0.0.1:8188/object_info', (resp) => {
let data = ''
resp.on('data', (chunk) => {
data += chunk
})
resp.on('end', () => {
// Modify the response data to add some checkpoints
const objectInfo = JSON.parse(data)
objectInfo.CheckpointLoaderSimple.input.required.ckpt_name[0] = [
'model1.safetensors',
'model2.ckpt'
]
objectInfo.VAELoader.input.required.vae_name[0] = [
'vae1.safetensors',
'vae2.ckpt'
]
data = JSON.stringify(objectInfo, undefined, '\t')
const outDir = resolve('./tests-ui/data')
if (!existsSync(outDir)) {
mkdirSync(outDir)
}
const outPath = resolve(outDir, 'object_info.json')
console.log(
`Writing ${Object.keys(objectInfo).length} nodes to ${outPath}`
)
writeFileSync(outPath, data, {
encoding: 'utf8'
})
res()
})
})
.on('error', rej)
})
}
setup()

View File

@@ -1,7 +1,4 @@
// @ts-strict-ignore
import fs from 'fs'
import path from 'path'
import {
type ComfyNodeDef,
validateComfyNodeDef
@@ -76,19 +73,4 @@ describe('validateNodeDef', () => {
})
}
)
it('Should accept all built-in node definitions', async () => {
const nodeDefs = Object.values(
JSON.parse(
fs.readFileSync(
path.resolve('./tests-ui/data/object_info.json'),
'utf8'
)
)
)
for (const nodeDef of nodeDefs) {
expect(validateComfyNodeDef(nodeDef)).not.toBeNull()
}
})
})