Read about our upcoming Code of Conduct on this issue

api_test.lua 43.6 KB
Newer Older
1
-- Script API Tests
2
-- Test routines for the scripting API
3
4
-- When adding new Lua APIs, tests for them should go here.

5
require("logger")
6

7
8
9
-- global logger
test_log = nil

10
function printf(s,...)
11
    test_log:writef(s,...)
12
13
14
15

    if not console.visible then
        display.notify_box(s:format(...), 5000)
    end
16
17
end

18
19
function request_mode(mode, mode_str)
    while camera.mode ~= mode do
20
        printf("Please switch to %s mode.\n", mode_str, mode)
21
        while camera.mode ~= mode do
alex@thinkpad's avatar
alex@thinkpad committed
22
            console.show(); assert(console.visible)
23
24
25
26
27
            msleep(1000)
        end
    end
end

a1ex's avatar
a1ex committed
28
29
30
31
32
function round(x)
    -- https://scriptinghelpers.org/questions/4850/how-do-i-round-numbers-in-lua-answered
    return x + 0.5 - (x + 0.5) % 1
end

33
34
35
36
37
38
39
40
41
42
function print_table(t)
    test_log:write(string.format("%s = ", t))
    local s,e = xpcall(function()
        test_log:serialize(_G[t])
    end, debug.traceback)
    if s == false then
        test_log:write(e)
    end
end

43
44
45
46
-- used for testing Lua 'strict' mode
declared_var = nil

function strict_tests()
47
    printf("Strict mode tests...\n")
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

    -- this should work
    declared_var = 5
    assert(declared_var == 5)

    -- this should raise error
    local s,e = pcall(function() undeclared_var = 7 end)
    assert(s == false)
    assert(e:find("assign to undeclared variable 'undeclared_var'"))

    -- same here
    local s,e = pcall(function() print(undeclared_var) end)
    assert(s == false)
    assert(e:find("variable 'undeclared_var' is not declared"))

63
64
    printf("Strict mode tests passed.\n")
    printf("\n")
65
66
67
end

function generic_tests()
68
    printf("Generic tests...\n")
69
    print_table("arg")
70
71
72
73
74
75
76
77
78
79
80
81
82
83
    print_table("camera")
    print_table("event")
    print_table("console")
    print_table("lv")
    print_table("lens")
    print_table("display")
    print_table("key")
    print_table("menu")
    print_table("movie")
    print_table("dryos")
    print_table("interval")
    print_table("battery")
    print_table("task")
    print_table("property")
84
85
    printf("Generic tests completed.\n")
    printf("\n")
86
87
end

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
function stdio_test()
    local s,e
    
    -- these should print to console
    -- fixme: they should output this:
    -- Hello, stderr. Hello, stdout. 
    -- Hello again, stdout.
    -- Hello again, stderr.
    io.write("Hello, stdout. ")
    io.stderr:write("Hello, stderr. ")
    io.write("\n")

    io.write("Hello again, stdout.\n")
    io.stderr:write("Hello again, stderr.\n")

    -- not implemented
    -- I expected error, but...
    local inp = io.read("*line")
    print("From stdin:", inp)
end

function copy_test(src, dst)
110
    printf("Copy test: %s -> %s\n", src, dst)
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    
    -- will this copy without errors? we'll see
    -- read source file
    local fin = io.open(src, "rb")
    local data = fin:read("*all")
    fin:close()

    -- save a copy
    local fout = io.open(dst, "w")
    fout:write(data)
    fout:close()

    -- verify
    fin = io.open(dst, "rb")
    local check = fin:read("*all")
    local size = fin:seek("end", 0)
    fin:close()
    assert(data == check)
    assert(size == #data)
    
    -- delete the copy
    assert(dryos.remove(dst) == true)
    
    -- check if it was deleted
alex@thinkpad's avatar
alex@thinkpad committed
135
    assert(io.open(dst, "rb") == nil)
136
137
138
139

    -- it should return false this time
    assert(dryos.remove(dst) == false)

140
    printf("Copy test OK\n")
141
142
143
end

function append_test(file)
144
    printf("Append test: %s\n", file)
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

    local data1 = "Allons enfants de la patrie\n"
    local data2 = "Le jour de gloire est arrivé !\n"
    
    -- make sure the file is not there
    dryos.remove(file)
    
    -- create it
    local fout = io.open(file, "a")
    fout:write(data1)
    fout:close()

    -- verify the contents
    local fin = io.open(file, "r")
    local check = fin:read("*all")
    fin:close()
alex@thinkpad's avatar
alex@thinkpad committed
161
    assert(check == data1)
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177

    -- reopen it to append something
    fout = io.open(file, "a")
    fout:write(data2)
    fout:close()

    -- verify the contents
    fin = io.open(file, "r")
    check = fin:read("*all")
    fin:close()
    assert(check == data1 .. data2)

    -- delete it
    assert(dryos.remove(file) == true)
    
    -- check if it was deleted
alex@thinkpad's avatar
alex@thinkpad committed
178
    assert(io.open(file, "rb") == nil)
179

180
    printf("Append test OK\n")
181
182
end

183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
function rename_test(src, dst)
    printf("Rename test: %s -> %s\n", src, dst)

    local data = "Millions saw the apple fall, " ..
        "but Newton was the one who asked why."
    
    -- create the source file
    local fout = io.open(src, "w")
    fout:write(data)
    fout:close()

    -- verify the contents
    local fin = io.open(src, "r")
    local check = fin:read("*all")
    fin:close()
    assert(check == data)

    -- rename it
    assert(dryos.rename(src, dst) == true)

    -- verify the contents
    fin = io.open(dst, "r")
    check = fin:read("*all")
    fin:close()
    assert(check == data)
    
    -- check if the source file was deleted
    assert(io.open(src, "rb") == nil)

    -- cleanup: delete the renamed file
    assert(dryos.remove(dst) == true)
    
    -- check if it was deleted
    assert(io.open(dst, "rb") == nil)

    printf("Rename test OK\n")
end

221
function test_io()
alex@thinkpad's avatar
alex@thinkpad committed
222
    printf("Testing file I/O...\n")
223
224
225
    stdio_test()
    copy_test("autoexec.bin", "tmp.bin")
    append_test("tmp.txt")
226
227
    rename_test("apple.txt", "banana.txt")
    rename_test("apple.txt", "ML/banana.txt")
alex@thinkpad's avatar
alex@thinkpad committed
228
229
230

    printf("File I/O tests completed.\n")
    printf("\n")
231
232
end

233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
function test_menu()
    printf("Testing menu API...\n")

    menu.open()
    assert(menu.select("Expo", "ISO"))

    -- menu values should be set-able as string (displayed value)
    -- or as integer (internal value)
    assert(menu.set("Expo", "ISO", "200"))
    assert(camera.iso.value == 200)
    msleep(1000)

    -- int should be promoted to string because of custom logic in Expo->ISO menu
    assert(menu.set("Expo", "ISO", 1600))
    assert(camera.iso.value == 1600)
    msleep(1000)

    -- move selection (not required for set to work; just for user interface)
    assert(menu.select("Expo", "Picture Style"))

    -- picture styles should be set-able as string
    -- numeric works too, as it sets the internal index
    assert(menu.set("Expo", "Picture Style", "Portrait"))
256
    assert(menu.get("Expo", "Picture Style", "") == "Portrait")
257
258
    msleep(1000)
    assert(menu.set("Expo", "Picture Style", 5)) -- OK, selects Neutral
259
260
261
    -- skip string test, as other camera models may use different indices
    msleep(1000)
    assert(menu.set("Expo", "Picture Style", "Landscape"))
262
    assert(menu.get("Expo", "Picture Style", "") == "Landscape")
263
    msleep(1000)
264
    assert(menu.set("Expo", "Picture Style", 1234) == false) -- should fail, out of range
265
    assert(menu.get("Expo", "Picture Style", "") == "Landscape") -- old selection should not change
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
    msleep(1000)

    assert(menu.select("Overlay"))
    assert(menu.select("Movie"))
    assert(menu.select("Shoot"))

    assert(menu.select("Shoot", "Advanced Bracket"))

    -- boolean items should be set-able as int (0 or 1)
    assert(menu.set("Shoot", "Advanced Bracket", 1))
    assert(menu.get("Shoot", "Advanced Bracket") == 1)
    msleep(1000)

    -- or as string (if the user interface displays "ON" or "OFF")
    -- here, actual string will be "ON, 10s" or similar (also accepted)
    assert(menu.set("Shoot", "Intervalometer", "ON"))
    assert(menu.get("Shoot", "Intervalometer") == 1)
    msleep(1000)

    -- turning off should be straightforward
    assert(menu.set("Shoot", "Advanced Bracket", "OFF"))
    assert(menu.get("Shoot", "Advanced Bracket") == 0)
288
    assert(menu.get("Shoot", "Advanced Bracket", "") == "OFF")
289
290
291
292
    msleep(1000)

    assert(menu.set("Shoot", "Intervalometer", 0))
    assert(menu.get("Shoot", "Intervalometer") == 0)
293
    assert(menu.get("Shoot", "Intervalometer", "") == "OFF")
294
295
296
297
298
299
300
    msleep(1000)

    -- move to Intervalometer menu
    assert(menu.select("Shoot", "Intervalometer"))
    msleep(1000)

    -- enter the submenu
301
    assert(menu.select("Intervalometer", "Take a pic every"))
302
303
304
305
306
307
308
309
310
    msleep(1000)

    -- note: setting menu by string works by brute force
    -- that is, trying every possible value and comparing the string
    -- the range for this menu is huge, so it only checks round values
    -- for speed reasons (so entering 1m10s will fail)
    -- smaller ranges are OK for trying every single value
    assert(menu.set("Intervalometer", "Take a pic every", "1m30s"))
    assert(menu.get("Intervalometer", "Take a pic every") == 90)
311
    assert(menu.get("Intervalometer", "Take a pic every", "") == "1m30s")
312
313
314
315
316
    msleep(1000)

    -- actual string will be 10s
    assert(menu.set("Intervalometer", "Take a pic every", "10"))
    assert(menu.get("Intervalometer", "Take a pic every") == 10)
317
    assert(menu.get("Intervalometer", "Take a pic every", "") == "10s")
318
319
320
321
322
    msleep(1000)

    -- integer should work as well - e.g. 1m10s should work now
    assert(menu.set("Intervalometer", "Take a pic every", 70))
    assert(menu.get("Intervalometer", "Take a pic every") == 70)
323
    assert(menu.get("Intervalometer", "Take a pic every", "") == "1m10s")
324
325
326
    msleep(1000)

    -- out of range, should fail
327
    assert(menu.set("Intervalometer", "Take a pic every", 7000000) == false)
328
    assert(menu.get("Intervalometer", "Take a pic every") == 70)
329
    assert(menu.get("Intervalometer", "Take a pic every", "") == "1m10s")
330
331
332
    msleep(1000)

    -- exit submenu
333
    assert(menu.select("Shoot", "Intervalometer"))
334
335
    msleep(1000)

336
337
    -- let's try some more submenus
    -- todo: more multi-level submenus
338
339
340
341
342
343
344
345
346
    assert(menu.select("Advanced Bracket", "Frames")); msleep(1000)
    assert(menu.select("Advanced Bracket", "Sequence")); msleep(1000)
    assert(menu.select("Advanced Bracket", "ISO shifting")); msleep(1000)
    assert(menu.select("Bulb Timer", "Exposure duration")); msleep(1000)
    assert(menu.select("Shoot Preferences", "Snap Simulation")); msleep(1000)
    assert(menu.select("Misc key settings", "Sticky HalfShutter")); msleep(1000)
    assert(menu.select("Play mode actions", "Trigger key(s)")); msleep(1000)
    assert(menu.select("LiveView zoom tweaks", "Zoom on HalfShutter")); msleep(1000)
    assert(menu.select("Lens info", "Lens ID")); msleep(1000)
347
348
    assert(menu.select("Shoot", "Intervalometer")); msleep(1000)

349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
    -- selection in the Modified menu
    -- note: we have already modified "Take a pic every"
    -- so at least this setting should appear there
    assert(menu.set("Shoot", "Intervalometer", "ON")); msleep(1000)
    assert(menu.select("Modified", "Intervalometer")); msleep(1000)             -- we should find Intervalometer in the Modified menu
    assert(menu.select("Modified", "Take a pic every")); msleep(1000)           -- same for "Take pic every" (smaller font)
    assert(menu.select("Modified", "Intervalometer")); msleep(1000)             -- scroll back to intervalometer
    assert(menu.set("Shoot", "Intervalometer", "OFF")); msleep(1000)            -- turn it off
    assert(menu.select("Modified", "Take a pic every") == false); msleep(1000)  -- now "Take a pic every" should disappear (submenus are only expanded when the main entry is on)
    assert(menu.set("Shoot", "Intervalometer", "ON")); msleep(1000)             -- re-enable intervalometer
    assert(menu.select("Modified", "Take a pic every")); msleep(1000)           -- now "Take a pic every" should re-appear
    assert(menu.set("Intervalometer", "Take a pic every", 10)); msleep(1000)    -- set "Take a pic every" back to default
    assert(menu.select("Modified", "Intervalometer")); msleep(1000)             -- scroll back to intervalometer
    assert(menu.set("Shoot", "Intervalometer", "OFF")); msleep(1000)            -- turn it off
    assert(menu.select("Modified", "Take a pic every") == false); msleep(1000)  -- now "Take a pic every" should disappear
    assert(menu.select("Shoot", "Intervalometer")); msleep(1000)                -- move back to Shoot -> Intervalometer
    assert(menu.select("Intervalometer", "Take a pic every")); msleep(1000)     -- set other intervalometer settings to default (just in case)
    assert(menu.select("Intervalometer", "Start trigger")); msleep(1000)        -- the menu.select calls are just for visual effect
    assert(menu.set("Intervalometer", "Start trigger", "Leave Menu")); msleep(500);
    assert(menu.select("Intervalometer", "Start after")); msleep(500)
    assert(menu.set("Intervalometer", "Start after", "3s")); msleep(500);
    assert(menu.select("Intervalometer", "Stop after")); msleep(500)
    assert(menu.set("Intervalometer", "Stop after", "Disabled")); msleep(500);
    assert(menu.select("Shoot", "Intervalometer")); msleep(1000)
    assert(menu.select("Modified", "Intervalometer") == false); msleep(1000)    -- now Modified -> Intervalometer should disappear

375
    -- non-existent menus; should fail
376
377
378
379
380
381
382
383
    assert(menu.select("Dinosaur") == false)
    assert(menu.select("Shoot", "Crocodile") == false)

    -- menu.get/set return nil if the menu was not found
    assert(menu.get("Shoot", "Introvolometer") == nil)
    assert(menu.get("Shoot", "Brack", "") == nil)
    assert(menu.set("Shoot", "Introvolometer", 1) == nil)
    assert(menu.set("Shoot", "Introvolometer", "OFF") == nil)
384
385
386
387
388
389
390
391
392
393
394
395
396

    menu.close()

    -- exercise the menu backend a bit
    for i = 1,5 do
        menu.open()
        menu.close()
    end

    printf("Menu tests completed.\n")
    printf("\n")
end

397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
function taskA()
    printf("Task A started.\n")
    local i
    for i = 1,100 do
        printf("Task A: %d\n", i)
        task.yield(math.random(10,50))
    end
end

function taskB()
    printf("Task B started.\n")
    local i
    for i = 1,100 do
        printf("Task B: %d\n", i)
        task.yield(math.random(10,50))
    end
end

415
416
417
418
419
420
421
422
423
424
425
426
function taskC()
    printf("Task C started.\n")
    msleep(math.random(10,50))
    printf("Task C finished.\n")
end

function taskD()
    io.write("Task D started.\n")
    msleep(math.random(10,50))
    io.write("Task D finished.\n")
end

427
428
429
function test_multitasking()
    printf("Testing multitasking...\n")

430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
    -- FIXME: this fails
    --task.create(taskA)
    --task.create(taskB)
    --task.yield(5000)
    
    printf("Only one task allowed to interrupt...\n")

    -- small test with logging to file
    for i = 1,10 do
        task.create(taskC)
        printf("Main task yielding.\n")
        task.yield(math.random(10,50))
        printf("Main task back.\n")
    end

    -- larger test with logging to console only
    for i = 1,1000 do
        task.create(taskD)
        io.write("Main task yielding.\n")
        task.yield(math.random(10,50))
        io.write("Main task back.\n")
    end

    task.yield(500)
454
455
456
457
458

    printf("Multitasking tests completed.\n")
    printf("\n")
end

459
460
function test_keys()
    printf("Testing half-shutter...\n")
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
    for i = 1,10 do
        -- open Canon menu
        key.press(KEY.MENU)
        msleep(1000)
        -- fixme: expose things like QR_MODE, PLAY_MODE, enter_play_mode...
        assert(camera.state == 1)
        key.press(KEY.HALFSHUTTER)
        msleep(200)
        assert(key.last == KEY.HALFSHUTTER)
        msleep(1000)
        -- half-shutter should close Canon menu
        assert(camera.state == 0)
        key.press(KEY.UNPRESS_HALFSHUTTER)
        msleep(200)
        assert(key.last == KEY.UNPRESS_HALFSHUTTER)
    end
477
478
479
    printf("Half-shutter test OK.\n")
    
    -- todo: test other key codes? press/unpress events?
alex@thinkpad's avatar
alex@thinkpad committed
480
    printf("\n")
481
482
end

483
function test_camera_exposure()
alex@thinkpad's avatar
alex@thinkpad committed
484
    printf("Testing exposure settings...\n")
485
486
487
488
489
490
    printf("Camera    : %s (%s) %s\n", camera.model, camera.model_short, camera.firmware)
    printf("Lens      : %s\n", lens.name)
    printf("Shoot mode: %s\n", camera.mode)
    printf("Shutter   : %s (raw %s, %ss, %sms, apex %s)\n", camera.shutter, camera.shutter.raw, camera.shutter.value, camera.shutter.ms, camera.shutter.apex)
    printf("Aperture  : %s (raw %s, f/%s, apex %s)\n", camera.aperture, camera.aperture.raw, camera.aperture.value, camera.aperture.apex)
    printf("Av range  : %s..%s (raw %s..%s, f/%s..f/%s, apex %s..%s)\n",
491
492
493
494
495
        camera.aperture.min, camera.aperture.max,
        camera.aperture.min.raw, camera.aperture.max.raw,
        camera.aperture.min.value, camera.aperture.max.value,
        camera.aperture.min.apex, camera.aperture.max.apex
    )
496
497
498
    printf("ISO       : %s (raw %s, %s, apex %s)\n", camera.iso, camera.iso.raw, camera.iso.value, camera.iso.apex)
    printf("EC        : %s (raw %s, %s EV)\n", camera.ec, camera.ec.raw, camera.ec.value)
    printf("Flash EC  : %s (raw %s, %s EV)\n", camera.flash_ec, camera.flash_ec.raw, camera.flash_ec.value)
499

500
    request_mode(MODE.M, "M")
501
    local old_value = camera.shutter.raw
502
    printf("Setting shutter to random values...\n")
503
    for k = 1,100 do
504
505
        local method = math.random(1,4)
        local d = nil
506
507
        if method == 1 then
            local s = math.random(1,30)
508
509
510
511
512
            if math.random(1,2) == 1 then
                camera.shutter.value = s
            else
                camera.shutter = s
            end
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
            d = math.abs(math.log(camera.shutter.value,2) - math.log(s,2))
        elseif method == 2 then
            local ms = math.random(1,30000)
            camera.shutter.ms = ms
            d = math.abs(math.log(camera.shutter.ms,2) - math.log(ms,2))
        elseif method == 3 then
            local apex = math.random(-5*100,12*100)/100
            camera.shutter.apex = apex
            d = math.abs(camera.shutter.apex - apex)
        elseif method == 4 then
            local raw = math.random(16,152)
            camera.shutter.raw = raw
            d = math.abs(camera.shutter.raw - raw) / 8
        end

        -- difference between requested and actual shutter speed should be max 1.5/8 EV
        -- (most cameras accept 0, 3/8, 4/8, 5/8, 1 EV, the largest gap being 3/8 EV,
        --  and you should always get the nearest valid shutter speed)
        if d > 1.5/8 + 1e-3 then
532
            printf("Error: shutter delta %s EV\n", d)
533
534
535
536
        end

        -- seconds and ms fields should be consistent
        if math.abs(camera.shutter.value - camera.shutter.ms/1000) > 1e-3 then
537
            printf("Error: shutter %ss != %sms\n", camera.shutter.value, camera.shutter.ms)
538
539
540
        end
        
        -- seconds and apex should be consistent
541
        -- see http://dougkerr.net/Pumpkin/articles/APEX.pdf
542
        local expected_apex = math.log(1/camera.shutter.value,2)
543
        if math.abs(expected_apex - camera.shutter.apex) > 0.01 then
544
            printf("Error: shutter %ss != Tv%s, expected %s\n", camera.shutter.value, camera.shutter.apex, expected_apex)
545
546
547
548
549
        end
        
        -- setting shutter to the same value, using any method (s,ms,apex,raw)
        -- should not change anything
        for i,field in pairs{"value","ms","apex","raw"} do
550
551
552
553
554
            local current = {}
            current.apex  = camera.shutter.apex
            current.value = camera.shutter.value
            current.ms    = camera.shutter.ms
            current.raw   = camera.shutter.raw
555
556
557
            
            -- ms is integer, so tests at very low values will fail because of roundoff errors
            -- however, if we have set the shutter in ms from the beginning, it will work fine
558
            if field == "ms" and current.ms < 10 and method ~= 2 then
559
560
561
562
                -- how do you write "continue" in Lua?!
                field = "value"
            end
            
563
            camera.shutter[field] = current[field]
564
            
565
            if camera.shutter.value ~= current.value then
566
                printf("Error: shutter set to %s=%s, got %ss, expected %ss\n", field, current[field], camera.shutter.value, current.value)
567
            end
568
            if camera.shutter.ms ~= current.ms then
569
                printf("Error: shutter set to %s=%s, got %sms, expected %sms\n", field, current[field], camera.shutter.ms, current.ms)
570
            end
571
            if camera.shutter.apex ~= current.apex then
572
                printf("Error: shutter set to %s=%s, got Tv%s, expected Tv%s\n", field, current[field], camera.shutter.apex, current.apex)
573
            end
574
            if camera.shutter.raw ~= current.raw then
575
                printf("Error: shutter set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.shutter.raw, current.raw)
576
577
578
            end
        end
    end
579
    camera.shutter.raw = old_value
580

581
    request_mode(MODE.M, "M")
582
    old_value = camera.iso.raw
583
    printf("Setting ISO to random values...\n")
584
    for k = 1,100 do
585
586
        local method = math.random(1,3)
        local d = nil
587
588
        if method == 1 then
            local iso = math.random(100, 6400)
589
590
591
592
593
            if math.random(1,2) == 1 then
                camera.iso.value = iso
            else
                camera.iso = iso
            end
594
595
596
597
598
599
600
601
602
603
604
605
606
            d = math.abs(math.log(camera.iso.value,2) - math.log(iso,2))
        elseif method == 2 then
            local apex = math.random(5*100,11*100)/100
            camera.iso.apex = apex
            d = math.abs(camera.iso.apex - apex)
        elseif method == 3 then
            local raw = math.random(72, 120)
            camera.iso.raw = raw
            d = math.abs(camera.iso.raw - raw) / 8
        end

        -- difference between requested and actual ISO should be max 1/3 EV
        if d > 1/3 then
607
            printf("Error: ISO delta %s EV\n", d)
608
609
610
        end

        -- ISO and Sv (APEX) should be consistent
611
        local expected_apex = math.log(camera.iso.value/3.125, 2)
612
        if math.abs(expected_apex - camera.iso.apex) > 0.2 then
613
            printf("Error: ISO %s != Sv%s, expected %s\n", camera.iso.value, camera.iso.apex, expected_apex)
614
615
616
617
618
        end

        -- setting ISO to the same value, using any method (value,apex,raw)
        -- should not change anything
        for i,field in pairs{"value","apex","raw"} do
619
620
621
622
            local current = {}
            current.apex  = camera.iso.apex
            current.value = camera.iso.value
            current.raw   = camera.iso.raw
623
            
624
            camera.iso[field] = current[field]
625
            
626
            if camera.iso.value ~= current.value then
627
                printf("Error: ISO set to %s=%s, got %s, expected %s\n", field, current[field], camera.iso.value, current.value)
628
            end
629
            if camera.iso.apex ~= current.apex then
630
                printf("Error: ISO set to %s=%s, got Sv%s, expected Sv%s\n", field, current[field], camera.iso.apex, current.apex)
631
            end
632
            if camera.iso.raw ~= current.raw then
633
                printf("Error: ISO set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.iso.raw, current.raw)
634
635
636
            end
        end
    end
637
    camera.iso.raw = old_value
638

a1ex's avatar
a1ex committed
639
    if camera.aperture.min.raw == camera.aperture.max.raw then
640
        printf("This lens does not have variable aperture (skipping test).\n")
a1ex's avatar
a1ex committed
641
642
    else
        request_mode(MODE.M, "M")
643
        old_value = camera.aperture.raw
644
        printf("Setting aperture to random values...\n")
a1ex's avatar
a1ex committed
645
        for k = 1,100 do
646
647
648
            local method = math.random(1,3)
            local d = nil
            local extra_tol = 0
a1ex's avatar
a1ex committed
649
650
            if method == 1 then
                local av = math.random(round(camera.aperture.min.value*10), round(camera.aperture.max.value*10)) / 10
651
652
653
654
655
                if math.random(1,2) == 1 then
                    camera.aperture.value = av
                else
                    camera.aperture = av
                end
a1ex's avatar
a1ex committed
656
                d = math.abs(math.log(camera.aperture.value,2) - math.log(av,2)) * 2
657
658
                -- when checking the result, allow a larger difference (0.1 units) - see note below
                extra_tol = math.abs(math.log(av,2) - math.log(av-0.1,2)) * 2
a1ex's avatar
a1ex committed
659
660
661
662
663
664
665
666
667
668
669
            elseif method == 2 then
                local apex = math.random(round(camera.aperture.min.apex*100), round(camera.aperture.max.apex*100)) / 100
                camera.aperture.apex = apex
                d = math.abs(camera.aperture.apex - apex)
            elseif method == 3 then
                local raw = math.random(camera.aperture.min.raw, camera.aperture.max.raw)
                camera.aperture.raw = raw
                d = math.abs(camera.aperture.raw - raw) / 8
            end

            -- difference between requested and actual aperture should be max 1.5/8 EV
670
671
672
            -- note: when using F-numbers, the difference may be larger, because of the rounding done
            -- to match Canon values (e.g. raw 48 would be f/5.66 (f/5.7), instead of Canon's f/5.6)
            if (d > 1.5/8 + extra_tol) then
alex@thinkpad's avatar
alex@thinkpad committed
673
                printf("Error: aperture delta %s EV (expected < %s, %s, method=%d)\n", d, 1.5/8 + extra_tol, camera.aperture, method)
a1ex's avatar
a1ex committed
674
675
676
            end

            -- aperture and Av (APEX) should be consistent
677
            local expected_apex = math.log(camera.aperture.value, 2) * 2
a1ex's avatar
a1ex committed
678
            if math.abs(expected_apex - camera.aperture.apex) > 0.2 then
679
                printf("Error: aperture %s != Av%s, expected %s\n", camera.aperture.value, camera.aperture.apex, expected_apex)
a1ex's avatar
a1ex committed
680
681
682
683
684
            end

            -- setting aperture to the same value, using any method (value,apex,raw)
            -- should not change anything
            for i,field in pairs{"value","apex","raw"} do
685
686
687
688
                local current = {}
                current.apex  = camera.aperture.apex
                current.value = camera.aperture.value
                current.raw   = camera.aperture.raw
a1ex's avatar
a1ex committed
689
                
690
                camera.aperture[field] = current[field]
a1ex's avatar
a1ex committed
691
                
692
                if camera.aperture.value ~= current.value then
693
                    printf("Error: aperture set to %s=%s, got %s, expected %s\n", field, current[field], camera.aperture.value, current.value)
a1ex's avatar
a1ex committed
694
                end
695
                if camera.aperture.apex ~= current.apex then
696
                    printf("Error: aperture set to %s=%s, got Sv%s, expected Sv%s\n", field, current[field], camera.aperture.apex, current.apex)
a1ex's avatar
a1ex committed
697
                end
698
                if camera.aperture.raw ~= current.raw then
699
                    printf("Error: aperture set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.aperture.raw, current.raw)
a1ex's avatar
a1ex committed
700
701
702
                end
            end
        end
703
        camera.aperture.raw = old_value
a1ex's avatar
a1ex committed
704
705
    end

706
    request_mode(MODE.AV, "Av")
707
    old_value = camera.ec.raw
708
    printf("Setting EC to random values...\n")
709
    for k = 1,100 do
710
711
        local method = math.random(1,2)
        local d = nil
712
713
        if method == 1 then
            local ec = math.random(-2*100, 2*100) / 100
714
715
716
717
718
            if math.random(1,2) == 1 then
                camera.ec.value = ec
            else
                camera.ec = ec
            end
719
720
721
722
723
724
725
726
727
            d = math.abs(camera.ec.value - ec,2)
        elseif method == 2 then
            local raw = math.random(-16, 16)
            camera.ec.raw = raw
            d = math.abs(camera.ec.raw - raw) / 8
        end

        -- difference between requested and actual EC should be max 1.5/8 EV
        if d > 1.5/8 then
728
            printf("Error: EC delta %s EV\n", d)
729
730
731
        end

        -- EC and raw should be consistent
732
        local expected_ec = camera.ec.raw / 8
733
        if math.abs(expected_ec - camera.ec.value) > 0.001 then
734
            printf("Error: EC raw %s != %s EV, expected %s EV\n", camera.ec.raw, camera.ec.value, expected_ec)
735
736
737
738
739
        end

        -- setting EC to the same value, using any method (value,raw)
        -- should not change anything
        for i,field in pairs{"value","raw"} do
740
741
742
            local current = {}
            current.value = camera.ec.value
            current.raw   = camera.ec.raw
743
            
744
            camera.ec[field] = current[field]
745
            
746
            if camera.ec.value ~= current.value then
747
                printf("Error: EC set to %s=%s, got %s, expected %s EV\n", field, current[field], camera.ec.value, current.value)
748
            end
749
            if camera.ec.raw ~= current.raw then
750
                printf("Error: EC set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.ec.raw, current.raw)
751
752
753
            end
        end
    end
754
    camera.ec.raw = old_value
755
756

    -- copy/paste & replace from EC (those two should behave in the same way)
757
    old_value = camera.flash_ec.raw
758
    printf("Setting Flash EC to random values...\n")
759
    for k = 1,100 do
760
761
        local method = math.random(1,2)
        local d = nil
762
763
        if method == 1 then
            local fec = math.random(-2*100, 2*100) / 100
764
765
766
767
768
            if math.random(1,2) == 1 then
                camera.flash_ec.value = fec
            else
                camera.flash_ec = fec
            end
769
770
771
772
773
774
775
776
777
            d = math.abs(camera.flash_ec.value - fec,2)
        elseif method == 2 then
            local raw = math.random(-16, 16)
            camera.flash_ec.raw = raw
            d = math.abs(camera.flash_ec.raw - raw) / 8
        end

        -- difference between requested and actual EC should be max 1.5/8 EV
        if d > 1.5/8 then
778
            printf("Error: FEC delta %s EV\n", d)
779
780
781
        end

        -- EC and raw should be consistent
782
        local expected_fec = camera.flash_ec.raw / 8
783
        if math.abs(expected_fec - camera.flash_ec.value) > 0.001 then
784
            printf("Error: FEC raw %s != %s EV, expected %s EV\n", camera.flash_ec.raw, camera.flash_ec.value, current.expected_fec)
785
786
787
788
789
        end

        -- setting EC to the same value, using any method (value,raw)
        -- should not change anything
        for i,field in pairs{"value","raw"} do
790
791
792
            local current = {}
            current.value = camera.flash_ec.value
            current.raw   = camera.flash_ec.raw
793
            
794
            camera.flash_ec[field] = current[field]
795
            
796
            if camera.flash_ec.value ~= current.value then
797
                printf("Error: FEC set to %s=%s, got %s, expected %s EV\n", field, current[field], camera.flash_ec.value, current.value)
798
            end
799
            if camera.flash_ec.raw ~= current.raw then
800
                printf("Error: FEC set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.flash_ec.raw, current.raw)
801
802
803
            end
        end
    end
804
    camera.flash_ec.raw = old_value
805

806
807
    printf("Exposure tests completed.\n")
    printf("\n")
808
809
end

810
811
812
813
814
815
816
817
818
819
820
821
function print_file_size(filename)
    local f = io.open(filename, "rb")
    if f then
        local size = f:seek("end", 0)
        f:close()
        printf("%s: %s\n", filename, size);
        return size
    else
        printf("%s not found.\n", filename);
    end
end

822
function test_camera_take_pics()
alex@thinkpad's avatar
alex@thinkpad committed
823
    printf("Testing picture taking functions...\n")
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
    local initial_file_num

    request_mode(MODE.M, "M")
    camera.shutter = 1/50
    msleep(2000)
    
    printf("Snap simulation test...\n")
    assert(menu.set("Shoot Preferences", "Snap Simulation", 1))
    initial_file_num = dryos.shooting_card.file_number
    camera.shoot()
    assert(dryos.shooting_card.file_number == initial_file_num)
    assert(menu.set("Shoot Preferences", "Snap Simulation", 0))

    msleep(2000)

    printf("Single picture...\n")
840
841
    -- let's also check if we can find the image file
    -- fixme: a way to check these routines when the image number wraps around at 10000
842
    initial_file_num = dryos.shooting_card.file_number
843
844
845
846
847
848
849
    local image_path = dryos.dcim_dir.path ..  dryos.image_prefix .. string.format("%04d", (initial_file_num + 1) % 10000)
    local image_path_cr2 = image_path .. ".CR2"
    local image_path_jpg = image_path .. ".JPG"
    -- the image file(s) should not be present before taking the picture :)
    assert(io.open(image_path_cr2, "rb") == nil)
    assert(io.open(image_path_jpg, "rb") == nil)

850
    camera.shoot()
851
852

    -- but either CR2 or JPG should be there afterwards (or maybe both)
853
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 1)
854
855
856
857
858
    camera.wait()
    local size_cr2 = print_file_size(image_path_cr2)
    local size_jpg = print_file_size(image_path_jpg)
    assert(size_cr2 or size_jpg)

859
860
861
862
863
    msleep(2000)

    printf("Two burst pictures...\n")
    printf("Ideally, the camera should be in some continuous shooting mode (not checked).\n")
    initial_file_num = dryos.shooting_card.file_number
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879

    -- we should also be able to change the file prefix
    local old_prefix = dryos.image_prefix
    dryos.image_prefix = "ABC_"
    assert(dryos.image_prefix == "ABC_")
    local image1_path = dryos.dcim_dir.path ..  dryos.image_prefix .. string.format("%04d", (initial_file_num + 1) % 10000)
    local image1_path_cr2 = image1_path .. ".CR2"
    local image1_path_jpg = image1_path .. ".JPG"
    local image2_path = dryos.dcim_dir.path ..  dryos.image_prefix .. string.format("%04d", (initial_file_num + 2) % 10000)
    local image2_path_cr2 = image2_path .. ".CR2"
    local image2_path_jpg = image2_path .. ".JPG"
    assert(io.open(image1_path_cr2, "rb") == nil)
    assert(io.open(image1_path_jpg, "rb") == nil)
    assert(io.open(image2_path_cr2, "rb") == nil)
    assert(io.open(image2_path_jpg, "rb") == nil)

880
    camera.burst(2)
881

882
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 2)
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
    camera.wait()
    local size1_cr2 = print_file_size(image1_path_cr2)
    local size1_jpg = print_file_size(image1_path_jpg)
    local size2_cr2 = print_file_size(image2_path_cr2)
    local size2_jpg = print_file_size(image2_path_jpg)
    assert(size1_cr2 or size1_jpg)
    assert(size2_cr2 or size2_jpg)
    dryos.image_prefix = ""     -- empty string restores the old prefix
    assert(dryos.image_prefix == old_prefix)

    -- let's try some more
    dryos.image_prefix = "XYZ_"
    assert(dryos.image_prefix == "XYZ_")
    dryos.image_prefix = "1234"
    assert(dryos.image_prefix == "1234")
    dryos.image_prefix = "$#@!"
    assert(dryos.image_prefix == "$#@!")
    dryos.image_prefix = ""
    assert(dryos.image_prefix == old_prefix)

    printf("Bracketed pictures...\n")
    initial_file_num = dryos.shooting_card.file_number
    camera.shutter.value = 1/500
    camera.shoot()
    camera.shutter.value = 1/50
    camera.shoot()
    camera.shutter.value = 1/5
    camera.shoot()
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 3)
    camera.wait()
    -- fixme: how to check metadata in the files?
    for i = dryos.shooting_card.file_number - 2, dryos.shooting_card.file_number do
        image_path = dryos.dcim_dir.path ..  dryos.image_prefix .. string.format("%04d", i % 10000)
        image_path_cr2 = image_path .. ".CR2"
        image_path_jpg = image_path .. ".JPG"
        local size_cr2 = print_file_size(image_path_cr2)
        local size_jpg = print_file_size(image_path_jpg)
        assert(size_cr2 or size_jpg)
    end
922
923
924
925
926
927

    msleep(2000)

    printf("Bulb picture...\n")
    local t0 = dryos.ms_clock
    initial_file_num = dryos.shooting_card.file_number
928
929
930
931
932
933
    image_path = dryos.dcim_dir.path ..  dryos.image_prefix .. string.format("%04d", (initial_file_num + 1) % 10000)
    image_path_cr2 = image_path .. ".CR2"
    image_path_jpg = image_path .. ".JPG"
    assert(io.open(image_path_cr2, "rb") == nil)
    assert(io.open(image_path_jpg, "rb") == nil)

934
    camera.bulb(10)
935

936
937
938
939
    local t1 = dryos.ms_clock
    local elapsed = t1 - t0
    printf("Elapsed time: %s\n", elapsed)
    -- we can't measure this time accurately, so we only do a very rough check
940
941
    -- slow cards may be an issue, so let's allow a wide error margin
    assert(elapsed > 9900 and elapsed < 30000)
942
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 1)
943
944
945
946
    camera.wait()
    local size_cr2 = print_file_size(image_path_cr2)
    local size_jpg = print_file_size(image_path_jpg)
    assert(size_cr2 or size_jpg)
947
948

    printf("Picture taking tests completed.\n")
alex@thinkpad's avatar
alex@thinkpad committed
949
    printf("\n")
950
    msleep(5000)
951
952
end

953
function test_lv()
954
    printf("Testing module 'lv'...\n")
955
    if lv.enabled then
956
        printf("LiveView is running; stopping...\n")
957
958
959
960
961
        lv.stop()
        assert(not lv.enabled, "LiveView did not stop")
        msleep(2000)
    end

962
    printf("Starting LiveView...\n")
963
964
    lv.start()
    assert(lv.enabled, "LiveView did not start")
965
    assert(not lens.autofocusing)
966
967

    msleep(2000)
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003

    -- lv.overlays status
    local function print_overlays_status()
        -- 1 = Canon overlays, 2 = ML overlays (when global draw is on), false = no overlays enabled
        -- using false instead of 0 to allow using in a conditional expression: if lv.overlays then ...
        printf("Overlays: %s\n", lv.overlays == 1 and "Canon" or lv.overlays == 2 and "ML" or "disabled");
    end

    console.hide(); assert(not console.visible)
    local old_gdr = menu.get("Overlay", "Global Draw")
    for i=1,10 do
        key.press(KEY.INFO)
        msleep(200); print_overlays_status()
        msleep(1000)
        if lv.overlays ~= 1 then
            -- Canon overlays disabled?
            -- Enable ML overlays
            assert(menu.set("Overlay", "Global Draw", "ON"))
            msleep(200); print_overlays_status()
            assert(lv.overlays == 2)
            msleep(1000)
            -- Disable ML overlays
            assert(menu.set("Overlay", "Global Draw", "OFF"))
            msleep(200); print_overlays_status()
            assert(lv.overlays == false)
            msleep(1000)
        end
    end
    -- restore original Global Draw setting
    assert(menu.set("Overlay", "Global Draw", old_gdr))
    assert(menu.get("Overlay", "Global Draw") == old_gdr)
    msleep(200); print_overlays_status()
    console.show(); assert(console.visible)

    msleep(2000)

1004
    for i,z in pairs{1, 5, 10, 5, 1, 10, 1} do
1005
        printf("Setting zoom to x%d...\n", z)
1006
1007
1008
1009
1010
        lv.zoom = z
        assert(lv.zoom == z, "Could not set zoom in LiveView ")
        lv.wait(5)
    end

1011
    printf("Pausing LiveView...\n")
1012
1013
1014
    lv.pause()
    assert(lv.enabled, "LiveView stopped")
    assert(lv.paused, "LiveView could not be paused")
1015
    assert(not lens.autofocusing)
1016
1017
1018

    msleep(2000)

1019
    printf("Resuming LiveView...\n")
1020
1021
1022
    lv.resume()
    assert(lv.enabled, "LiveView stopped")
    assert(not lv.paused, "LiveView could not be resumed")
1023
    assert(not lens.autofocusing)
1024
1025
1026

    msleep(2000)

1027
    printf("Stopping LiveView...\n")
1028
1029
1030
1031
1032
    lv.stop()

    assert(not lv.enabled, "LiveView did not stop")
    assert(not lv.paused,  "LiveView is disabled, can't be paused")
    assert(not lv.running, "LiveView is disabled, can't be running")
1033
    assert(not lens.autofocusing)
1034
1035
1036

    msleep(1000)

1037
1038
    printf("LiveView tests completed.\n")
    printf("\n")
1039
1040
end

1041
function test_lens_focus()
alex@thinkpad's avatar
alex@thinkpad committed
1042
1043
1044
    printf("\n")
    printf("Testing lens focus functionality...\n")
    
1045
    if lens.name == "" then
1046
        printf("This test requires an electronic lens.\n")
1047
1048
1049
1050
1051
        assert(not lens.af, "manual lenses can't autofocus")
        return
    end
    
    if not lens.af then
1052
1053
        printf("Please enable autofocus.\n")
        printf("(or, remove the lens from the camera to skip this test)\n")
1054
        while not lens.af and lens.name ~= "" do
alex@thinkpad's avatar
alex@thinkpad committed
1055
            console.show(); assert(console.visible)
1056
1057
            msleep(1000)
        end
1058
        msleep(1000)
1059
    end
1060

1061
1062
1063
    -- note: some lenses may be able to AF only in LiveView
    -- so let's check each mode regardless of the other

1064
    if not lv.running then
1065
1066
1067
1068
        if lens.af then
            printf("Autofocus outside LiveView...\n")
            assert(lens.autofocus())
        end
1069

1070
1071
1072
        lv.start()
        assert(lv.running)
    end
1073

1074
    if lens.af then
1075
        printf("Focus distance: %s\n",  lens.focus_distance)
1076

1077
        printf("Autofocus in LiveView...\n")
1078
        assert(not lens.autofocusing)
1079
        assert(lens.autofocus())
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
        assert(not lens.autofocusing)

        printf("Please trigger autofocus (half-shutter / AF-ON / * ).\n")
        for i = 1,2000 do
            msleep(10)
            if lens.autofocusing then
                printf("Autofocus triggered.\n")
                while lens.autofocusing do
                    msleep(10)
                end
                printf("Autofocus completed.\n")
                break
            end
            if i % 100 == 0 then
                printf("\b\b\b\b\b%d...", 20 - i // 100)
                io.flush()
            end
            if i // 100 == 20 then
                printf("\b\b\b\b\b")
                io.flush()
                assert(false, "Autofocus not triggered.\n")
            end
        end
1103

1104
        msleep(1000)
1105
1106
        printf("Focus distance: %s\n",  lens.focus_distance)

1107
1108
        -- note: focus direction is not consistent
        -- some lenses will focus to infinity, others to macro
1109
        printf("Focusing backward...\n")
1110
1111
        while lens.focus(-1,3,true) do end

1112
        -- note: focus distance and position may not be reported right away
1113
        msleep(500)
1114
1115
1116

        printf("Focus distance: %s\n",  lens.focus_distance)
        printf("Focus motor position: %d\n", lens.focus_pos)
1117
        
1118
        for i,step in pairs{3,2,1} do
1119
            for j,wait in pairs{true,false} do
1120
                printf("Focusing forward with step size %d, wait=%s...\n", step, wait)
1121
                local steps_front = 0
1122
                local focus_pos_0 = lens.focus_pos
1123
                while lens.focus(1,step,wait) do
1124
                    printf(".")
1125
1126
                    steps_front = steps_front + 1
                end
1127
1128
1129
                
                msleep(500)

1130
1131
                printf("\n")
                printf("Focus distance: %s\n",  lens.focus_distance)
1132
1133
1134
                printf("Focus motor position: %d\n", lens.focus_pos)
                local focus_pos_1 = lens.focus_pos

1135
1136
                msleep(500)
                
1137
                printf("Focusing backward with step size %d, wait=%s...\n", step, wait)
1138
                local steps_back = 0
1139
                while lens.focus(-1,step,wait) do
1140
                    printf(".")
1141
1142
                    steps_back = steps_back + 1
                end
1143
1144
1145
                
                msleep(500)

1146
1147
                printf("\n")
                printf("Focus distance: %s\n",  lens.focus_distance)
1148
1149
                printf("Focus motor position: %d\n", lens.focus_pos)
                local focus_pos_2 = lens.focus_pos
1150

1151
                msleep(500)
1152

1153
1154
1155
                local motor_steps_front = math.abs(focus_pos_1 - focus_pos_0)
                local motor_steps_back  = math.abs(focus_pos_2 - focus_pos_1)
                local motor_steps_lost  = math.abs(focus_pos_2 - focus_pos_0)
1156
                printf("Focus range: %s steps forward, %s steps backward. \n",  steps_front, steps_back)
1157
1158
1159
                printf("Motor steps: %s forward, %s backward, %s lost. \n",  motor_steps_front, motor_steps_back, motor_steps_lost)

                msleep(500)
1160
            end
1161
        end
1162
        printf("\nFocus test completed.\n")
1163
    else
alex@thinkpad's avatar