mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2024-11-27 02:33:10 +01:00
413 lines
16 KiB
JavaScript
413 lines
16 KiB
JavaScript
"use strict"
|
|
|
|
const JASMINE_SESSION_ID = `jasmine-${String(Date.now()).slice(8)}`
|
|
|
|
beforeEach(function () {
|
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 60 * 1000 // Test timeout after 15 minutes
|
|
jasmine.addMatchers({
|
|
toBeOneOf: function () {
|
|
return {
|
|
compare: function (actual, expected) {
|
|
return {
|
|
pass: expected.includes(actual)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
describe('stable-diffusion-ui', function() {
|
|
beforeEach(function() {
|
|
expect(typeof SD).toBe('object')
|
|
expect(typeof SD.serverState).toBe('object')
|
|
expect(typeof SD.serverState.status).toBe('string')
|
|
})
|
|
it('should be able to reach the backend', async function() {
|
|
expect(SD.serverState.status).toBe(SD.ServerStates.unavailable)
|
|
SD.sessionId = JASMINE_SESSION_ID
|
|
await SD.init()
|
|
expect(SD.isServerAvailable()).toBeTrue()
|
|
})
|
|
|
|
it('enfore the current task state', function() {
|
|
const task = new SD.Task()
|
|
expect(task.status).toBe(SD.TaskStatus.init)
|
|
expect(task.isPending).toBeTrue()
|
|
|
|
task._setStatus(SD.TaskStatus.pending)
|
|
expect(task.status).toBe(SD.TaskStatus.pending)
|
|
expect(task.isPending).toBeTrue()
|
|
expect(function() {
|
|
task._setStatus(SD.TaskStatus.init)
|
|
}).toThrowError()
|
|
|
|
task._setStatus(SD.TaskStatus.waiting)
|
|
expect(task.status).toBe(SD.TaskStatus.waiting)
|
|
expect(task.isPending).toBeTrue()
|
|
expect(function() {
|
|
task._setStatus(SD.TaskStatus.pending)
|
|
}).toThrowError()
|
|
|
|
task._setStatus(SD.TaskStatus.processing)
|
|
expect(task.status).toBe(SD.TaskStatus.processing)
|
|
expect(task.isPending).toBeTrue()
|
|
expect(function() {
|
|
task._setStatus(SD.TaskStatus.pending)
|
|
}).toThrowError()
|
|
|
|
task._setStatus(SD.TaskStatus.failed)
|
|
expect(task.status).toBe(SD.TaskStatus.failed)
|
|
expect(task.isPending).toBeFalse()
|
|
expect(function() {
|
|
task._setStatus(SD.TaskStatus.processing)
|
|
}).toThrowError()
|
|
expect(function() {
|
|
task._setStatus(SD.TaskStatus.completed)
|
|
}).toThrowError()
|
|
})
|
|
it('should be able to run tasks', async function() {
|
|
expect(typeof SD.Task.run).toBe('function')
|
|
const promiseGenerator = (function*(val) {
|
|
expect(val).toBe('start')
|
|
expect(yield 1 + 1).toBe(4)
|
|
expect(yield 2 + 2).toBe(8)
|
|
yield asyncDelay(500)
|
|
expect(yield 3 + 3).toBe(12)
|
|
expect(yield 4 + 4).toBe(16)
|
|
return 8 + 8
|
|
})('start')
|
|
const callback = function({value, done}) {
|
|
return {value: 2 * value, done}
|
|
}
|
|
expect(await SD.Task.run(promiseGenerator, {callback})).toBe(32)
|
|
})
|
|
it('should be able to queue tasks', async function() {
|
|
expect(typeof SD.Task.enqueue).toBe('function')
|
|
const promiseGenerator = (function*(val) {
|
|
expect(val).toBe('start')
|
|
expect(yield 1 + 1).toBe(4)
|
|
expect(yield 2 + 2).toBe(8)
|
|
yield asyncDelay(500)
|
|
expect(yield 3 + 3).toBe(12)
|
|
expect(yield 4 + 4).toBe(16)
|
|
return 8 + 8
|
|
})('start')
|
|
const callback = function({value, done}) {
|
|
return {value: 2 * value, done}
|
|
}
|
|
const gen = SD.Task.asGenerator({generator: promiseGenerator, callback})
|
|
expect(await SD.Task.enqueue(gen)).toBe(32)
|
|
})
|
|
it('should be able to chain handlers', async function() {
|
|
expect(typeof SD.Task.enqueue).toBe('function')
|
|
const promiseGenerator = (function*(val) {
|
|
expect(val).toBe('start')
|
|
expect(yield {test: '1'}).toEqual({test: '1', foo: 'bar'})
|
|
expect(yield 2 + 2).toEqual(8)
|
|
yield asyncDelay(500)
|
|
expect(yield 3 + 3).toEqual(12)
|
|
expect(yield {test: 4}).toEqual({test: 8, foo: 'bar'})
|
|
return {test: 8}
|
|
})('start')
|
|
const gen1 = SD.Task.asGenerator({generator: promiseGenerator, callback: function({value, done}) {
|
|
if (typeof value === "object") {
|
|
value['foo'] = 'bar'
|
|
}
|
|
return {value, done}
|
|
}})
|
|
const gen2 = SD.Task.asGenerator({generator: gen1, callback: function({value, done}) {
|
|
if (typeof value === 'number') {
|
|
value = 2 * value
|
|
}
|
|
if (typeof value === 'object' && typeof value.test === 'number') {
|
|
value.test = 2 * value.test
|
|
}
|
|
return {value, done}
|
|
}})
|
|
expect(await SD.Task.enqueue(gen2)).toEqual({test:32, foo: 'bar'})
|
|
})
|
|
describe('ServiceContainer', function() {
|
|
it('should be able to register providers', function() {
|
|
const cont = new ServiceContainer(
|
|
function foo() {
|
|
this.bar = ''
|
|
},
|
|
function bar() {
|
|
return () => 0
|
|
},
|
|
{ name: 'zero', definition: 0 },
|
|
{ name: 'ctx', definition: () => Object.create(null), singleton: true },
|
|
{ name: 'test',
|
|
definition: (ctx, missing, one, foo) => {
|
|
expect(ctx).toEqual({ran: true})
|
|
expect(one).toBe(1)
|
|
expect(typeof foo).toBe('object')
|
|
expect(foo.bar).toBeDefined()
|
|
expect(typeof missing).toBe('undefined')
|
|
return {foo: 'bar'}
|
|
}, dependencies: ['ctx', 'missing', 'one', 'foo']
|
|
}
|
|
)
|
|
const fooObj = cont.get('foo')
|
|
expect(typeof fooObj).toBe('object')
|
|
fooObj.ran = true
|
|
|
|
const ctx = cont.get('ctx')
|
|
expect(ctx).toEqual({})
|
|
ctx.ran = true
|
|
|
|
const bar = cont.get('bar')
|
|
expect(typeof bar).toBe('function')
|
|
expect(bar()).toBe(0)
|
|
|
|
cont.register({name: 'one', definition: 1})
|
|
const test = cont.get('test')
|
|
expect(typeof test).toBe('object')
|
|
expect(test.foo).toBe('bar')
|
|
})
|
|
})
|
|
it('should be able to stream data in chunks', async function() {
|
|
expect(SD.isServerAvailable()).toBeTrue()
|
|
const nbr_steps = 15
|
|
let res = await fetch('/render', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
"prompt": "a photograph of an astronaut riding a horse",
|
|
"negative_prompt": "",
|
|
"width": 128,
|
|
"height": 128,
|
|
"seed": Math.floor(Math.random() * 10000000),
|
|
|
|
"sampler": "plms",
|
|
"use_stable_diffusion_model": "sd-v1-4",
|
|
"num_inference_steps": nbr_steps,
|
|
"guidance_scale": 7.5,
|
|
|
|
"numOutputsParallel": 1,
|
|
"stream_image_progress": true,
|
|
"show_only_filtered_image": true,
|
|
"output_format": "jpeg",
|
|
|
|
"session_id": JASMINE_SESSION_ID,
|
|
}),
|
|
})
|
|
expect(res.ok).toBeTruthy()
|
|
const renderRequest = await res.json()
|
|
expect(typeof renderRequest.stream).toBe('string')
|
|
expect(renderRequest.task).toBeDefined()
|
|
|
|
// Wait for server status to update.
|
|
await SD.waitUntil(() => {
|
|
console.log('Waiting for %s to be received...', renderRequest.task)
|
|
return (!SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)])
|
|
}, 250, 10 * 60 * 1000)
|
|
// Wait for task to start on server.
|
|
await SD.waitUntil(() => {
|
|
console.log('Waiting for %s to start...', renderRequest.task)
|
|
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)] !== 'pending'
|
|
}, 250)
|
|
|
|
const reader = new SD.ChunkedStreamReader(renderRequest.stream)
|
|
const parseToString = reader.parse
|
|
reader.parse = function(value) {
|
|
value = parseToString.call(this, value)
|
|
if (!value || value.length <= 0) {
|
|
return
|
|
}
|
|
return reader.readStreamAsJSON(value.join(''))
|
|
}
|
|
reader.onNext = function({done, value}) {
|
|
console.log(value)
|
|
if (typeof value === 'object' && 'status' in value) {
|
|
done = true
|
|
}
|
|
return {done, value}
|
|
}
|
|
let lastUpdate = undefined
|
|
let stepCount = 0
|
|
let complete = false
|
|
//for await (const stepUpdate of reader) {
|
|
for await (const stepUpdate of reader.open()) {
|
|
console.log('ChunkedStreamReader received ', stepUpdate)
|
|
lastUpdate = stepUpdate
|
|
if (complete) {
|
|
expect(stepUpdate.status).toBe('succeeded')
|
|
expect(stepUpdate.output).toHaveSize(1)
|
|
} else {
|
|
expect(stepUpdate.total_steps).toBe(nbr_steps)
|
|
expect(stepUpdate.step).toBe(stepCount)
|
|
if (stepUpdate.step === stepUpdate.total_steps) {
|
|
complete = true
|
|
} else {
|
|
stepCount++
|
|
}
|
|
}
|
|
}
|
|
for(let i=1; i <= 5; ++i) {
|
|
res = await fetch(renderRequest.stream)
|
|
expect(res.ok).toBeTruthy()
|
|
const cachedResponse = await res.json()
|
|
console.log('Cache test %s received %o', i, cachedResponse)
|
|
expect(lastUpdate).toEqual(cachedResponse)
|
|
}
|
|
})
|
|
|
|
describe('should be able to make renders', function() {
|
|
beforeEach(function() {
|
|
expect(SD.isServerAvailable()).toBeTrue()
|
|
})
|
|
it('basic inline request', async function() {
|
|
let stepCount = 0
|
|
let complete = false
|
|
const result = await SD.render({
|
|
"prompt": "a photograph of an astronaut riding a horse",
|
|
"width": 128,
|
|
"height": 128,
|
|
"num_inference_steps": 10,
|
|
"show_only_filtered_image": false,
|
|
//"use_face_correction": 'GFPGANv1.3',
|
|
"use_upscale": "RealESRGAN_x4plus",
|
|
"session_id": JASMINE_SESSION_ID,
|
|
}, function(event) {
|
|
console.log(this, event)
|
|
if ('update' in event) {
|
|
const stepUpdate = event.update
|
|
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
|
expect(stepUpdate.status).toBe('succeeded')
|
|
expect(stepUpdate.output).toHaveSize(2)
|
|
} else {
|
|
expect(stepUpdate.step).toBe(stepCount)
|
|
if (stepUpdate.step === stepUpdate.total_steps) {
|
|
complete = true
|
|
} else {
|
|
stepCount++
|
|
}
|
|
}
|
|
}
|
|
})
|
|
console.log(result)
|
|
expect(result.status).toBe('succeeded')
|
|
expect(result.output).toHaveSize(2)
|
|
})
|
|
it('post and reader request', async function() {
|
|
const renderTask = new SD.RenderTask({
|
|
"prompt": "a photograph of an astronaut riding a horse",
|
|
"width": 128,
|
|
"height": 128,
|
|
"seed": SD.MAX_SEED_VALUE,
|
|
"num_inference_steps": 10,
|
|
"session_id": JASMINE_SESSION_ID,
|
|
})
|
|
expect(renderTask.status).toBe(SD.TaskStatus.init)
|
|
|
|
const timeout = -1
|
|
const renderRequest = await renderTask.post(timeout)
|
|
expect(typeof renderRequest.stream).toBe('string')
|
|
expect(renderTask.status).toBe(SD.TaskStatus.waiting)
|
|
expect(renderTask.streamUrl).toBe(renderRequest.stream)
|
|
|
|
await renderTask.waitUntil({state: SD.TaskStatus.processing, callback: () => console.log('Waiting for render task to start...') })
|
|
expect(renderTask.status).toBe(SD.TaskStatus.processing)
|
|
|
|
let stepCount = 0
|
|
let complete = false
|
|
//for await (const stepUpdate of renderTask.reader) {
|
|
for await (const stepUpdate of renderTask.reader.open()) {
|
|
console.log(stepUpdate)
|
|
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
|
expect(stepUpdate.status).toBe('succeeded')
|
|
expect(stepUpdate.output).toHaveSize(1)
|
|
} else {
|
|
expect(stepUpdate.step).toBe(stepCount)
|
|
if (stepUpdate.step === stepUpdate.total_steps) {
|
|
complete = true
|
|
} else {
|
|
stepCount++
|
|
}
|
|
}
|
|
}
|
|
expect(renderTask.status).toBe(SD.TaskStatus.completed)
|
|
expect(renderTask.result.status).toBe('succeeded')
|
|
expect(renderTask.result.output).toHaveSize(1)
|
|
})
|
|
it('queued request', async function() {
|
|
let stepCount = 0
|
|
let complete = false
|
|
const renderTask = new SD.RenderTask({
|
|
"prompt": "a photograph of an astronaut riding a horse",
|
|
"width": 128,
|
|
"height": 128,
|
|
"num_inference_steps": 10,
|
|
"show_only_filtered_image": false,
|
|
//"use_face_correction": 'GFPGANv1.3',
|
|
"use_upscale": "RealESRGAN_x4plus",
|
|
"session_id": JASMINE_SESSION_ID,
|
|
})
|
|
await renderTask.enqueue(function(event) {
|
|
console.log(this, event)
|
|
if ('update' in event) {
|
|
const stepUpdate = event.update
|
|
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
|
expect(stepUpdate.status).toBe('succeeded')
|
|
expect(stepUpdate.output).toHaveSize(2)
|
|
} else {
|
|
expect(stepUpdate.step).toBe(stepCount)
|
|
if (stepUpdate.step === stepUpdate.total_steps) {
|
|
complete = true
|
|
} else {
|
|
stepCount++
|
|
}
|
|
}
|
|
}
|
|
})
|
|
console.log(renderTask.result)
|
|
expect(renderTask.result.status).toBe('succeeded')
|
|
expect(renderTask.result.output).toHaveSize(2)
|
|
})
|
|
})
|
|
describe('# Special cases', function() {
|
|
it('should throw an exception on set for invalid sessionId', function() {
|
|
expect(function() {
|
|
SD.sessionId = undefined
|
|
}).toThrowError("Can't set sessionId to undefined.")
|
|
})
|
|
})
|
|
})
|
|
|
|
const loadCompleted = window.onload
|
|
let loadEvent = undefined
|
|
window.onload = function(evt) {
|
|
loadEvent = evt
|
|
}
|
|
if (!PLUGINS.SELFTEST) {
|
|
PLUGINS.SELFTEST = {}
|
|
}
|
|
loadUIPlugins().then(function() {
|
|
console.log('loadCompleted', loadEvent)
|
|
describe('@Plugins', function() {
|
|
it('exposes hooks to overide', function() {
|
|
expect(typeof PLUGINS.IMAGE_INFO_BUTTONS).toBe('object')
|
|
expect(typeof PLUGINS.TASK_CREATE).toBe('object')
|
|
})
|
|
describe('supports selftests', function() { // Hook to allow plugins to define tests.
|
|
const pluginsTests = Object.keys(PLUGINS.SELFTEST).filter((key) => PLUGINS.SELFTEST.hasOwnProperty(key))
|
|
if (!pluginsTests || pluginsTests.length <= 0) {
|
|
it('but nothing loaded...', function() {
|
|
expect(true).toBeTruthy()
|
|
})
|
|
return
|
|
}
|
|
for (const pTest of pluginsTests) {
|
|
describe(pTest, function() {
|
|
const testFn = PLUGINS.SELFTEST[pTest]
|
|
return Promise.resolve(testFn.call(jasmine, pTest))
|
|
})
|
|
}
|
|
})
|
|
})
|
|
loadCompleted.call(window, loadEvent)
|
|
})
|