Read about our upcoming Code of Conduct on this issue

api_test.lua 51.5 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
20
21
22
23
function alert()
    display.off()
    beep()
    display.on()
end

24
25
function request_mode(mode, mode_str)
    while camera.mode ~= mode do
26
        printf("Please switch to %s mode.\n", mode_str, mode)
27
        while camera.mode ~= mode do
alex@thinkpad's avatar
alex@thinkpad committed
28
            console.show(); assert(console.visible)
29
            sleep(1)
30
            alert()
31
32
33
34
        end
    end
end

a1ex's avatar
a1ex committed
35
36
37
38
39
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

40
41
42
43
44
45
46
47
48
49
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

50
51
52
53
-- used for testing Lua 'strict' mode
declared_var = nil

function strict_tests()
54
    printf("Strict mode tests...\n")
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

    -- 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"))

70
71
    printf("Strict mode tests passed.\n")
    printf("\n")
72
73
74
end

function generic_tests()
75
    printf("Generic tests...\n")
76
    print_table("arg")
77
78
79
80
81
82
83
84
85
86
87
88
89
90
    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")
91
92
    printf("Generic tests completed.\n")
    printf("\n")
93
94
end

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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)
117
    printf("Copy test: %s -> %s\n", src, dst)
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
    
    -- 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
142
    assert(io.open(dst, "rb") == nil)
143
144
145
146

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

147
    printf("Copy test OK\n")
148
149
150
end

function append_test(file)
151
    printf("Append test: %s\n", file)
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

    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
168
    assert(check == data1)
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

    -- 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
185
    assert(io.open(file, "rb") == nil)
186

187
    printf("Append test OK\n")
188
189
end

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
221
222
223
224
225
226
227
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

228
function test_io()
alex@thinkpad's avatar
alex@thinkpad committed
229
    printf("Testing file I/O...\n")
230
231
232
    stdio_test()
    copy_test("autoexec.bin", "tmp.bin")
    append_test("tmp.txt")
233
234
    rename_test("apple.txt", "banana.txt")
    rename_test("apple.txt", "ML/banana.txt")
alex@thinkpad's avatar
alex@thinkpad committed
235
236
237

    printf("File I/O tests completed.\n")
    printf("\n")
238
239
end

240
241
242
243
244
function test_camera_gui()
    printf("Testing Canon GUI functions...\n")

    camera.gui.play = true
    assert(camera.gui.play == true)
245
    assert(camera.gui.mode == 1)
246
247
248

    -- half-shutter should exit playback mode
    key.press(KEY.HALFSHUTTER)
249
    sleep(1)
250
    assert(camera.gui.play == false)
251
    assert(camera.gui.mode == 0)
252
253
254
255
256
257
258
259
260
261
262
263
264
    key.press(KEY.UNPRESS_HALFSHUTTER)

    -- randomly switch between PLAY, MENU and IDLE (with or without LiveView)
    for i = 1,100 do
        -- we can request MENU or PLAY mode from anywhere
        -- we can only exit these modes if we are already there
        if math.random(1,2) == 1 then
            if math.random(1,2) == 1 then
                printf("Enter PLAY mode...\n");
                camera.gui.play = true
                assert(camera.gui.play == true)
                assert(camera.gui.menu == false)
                assert(camera.gui.idle == false)
265
                assert(camera.gui.mode == 1)
266
267
268
269
270
271
            elseif camera.gui.play then
                printf("Exit PLAY mode...\n");
                camera.gui.play = false
                assert(camera.gui.play == false)
                assert(camera.gui.menu == false)
                assert(camera.gui.idle == true)
272
                assert(camera.gui.mode == 0)
273
274
275
276
277
278
279
280
            end
        else
            if math.random(1,2) == 1 then
                printf("Enter MENU mode...\n");
                camera.gui.menu = true
                assert(camera.gui.menu == true)
                assert(camera.gui.play == false)
                assert(camera.gui.idle == false)
281
                assert(camera.gui.mode == 2)
282
283
284
285
286
287
            elseif camera.gui.menu then
                printf("Exit MENU mode...\n");
                camera.gui.menu = false
                assert(camera.gui.menu == false)
                assert(camera.gui.play == false)
                assert(camera.gui.idle == true)
288
                assert(camera.gui.mode == 0)
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
            end
        end

        -- also play around with LiveView
        if camera.gui.menu == false and camera.gui.play == false then
            if math.random(1,2) == 1 then
                -- do something with LiveView, but not as often as switching MENU/PLAY
                if not lv.enabled then
                    printf("Start LiveView...\n");
                    lv.start()
                elseif lv.paused then
                    printf("Resume LiveView...\n");
                    lv.resume()
                elseif math.random(1,10) < 9 then
                    -- this gets taken less often than the next one, why?
                    -- fixme: biased random?
                    printf("Pause LiveView...\n");
                    lv.pause()
                else
                    printf("Stop LiveView...\n");
                    lv.stop()
                end
            end
        end
    end

    lv.stop()
316
    assert(not lv.enabled)
317
318
319
320
321

    printf("Canon GUI tests completed.\n")
    printf("\n")
end

322
function test_menu()
323
    printf("Testing ML menu API...\n")
324
325
326
327
328
329
330
331

    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)
332
    sleep(1)
333
334
335
336

    -- 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)
337
    sleep(1)
338
339
340
341
342
343
344

    -- 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"))
345
    assert(menu.get("Expo", "Picture Style") == "Portrait")
346
    sleep(1)
347
    assert(menu.set("Expo", "Picture Style", 5)) -- OK, selects Neutral
348
    -- skip string test, as other camera models may use different indices
349
    sleep(1)
350
    assert(menu.set("Expo", "Picture Style", "Landscape"))
351
    assert(menu.get("Expo", "Picture Style") == "Landscape")
352
    sleep(1)
353
    assert(menu.set("Expo", "Picture Style", 1234) == false) -- should fail, out of range
354
    assert(menu.get("Expo", "Picture Style") == "Landscape") -- old selection should not change
355
    sleep(1)
356
357

    assert(menu.select("Overlay"))
358

359
360
    assert(menu.select("Movie"))

361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
    -- perform the next test in LiveView
    menu.close()
    lv.start()
    assert(lv.enabled)
    assert(lv.running)
    menu.open()

    assert(menu.select("Movie", "FPS override"))
    assert(menu.set("Movie", "FPS override", "OFF"))            -- "OFF" and "ON" are boolean matches for zero/nonzero internal state
    assert(menu.get("Movie", "FPS override") == "OFF")
    assert(menu.set("FPS override", "Desired FPS", "23.976"))   -- this menu entry will print "23.976 (from 25)" or something like that
    assert(menu.get("FPS override", "Desired FPS"):sub(1,13) == "23.976 (from ")
    assert(menu.set("FPS override", "Desired FPS", "5"))        -- this menu entry will print "5 (from 30)" or something like that
    assert(menu.get("FPS override", "Desired FPS"):sub(1,8) == "5 (from ")
    assert(menu.set("FPS override", "Desired FPS", "10"))       -- this menu entry will print "10 (from 25)" or something like that
    assert(menu.get("FPS override", "Desired FPS"):sub(1,9) == "10 (from ")
    assert(menu.set("FPS override", "Optimize for", "Exact FPS")) -- nothing fancy here, just request exact frame rate
    assert(menu.get("FPS override", "Optimize for") == "Exact FPS")
    assert(menu.set("Movie", "FPS override", "ON"))             -- enable FPS override
    assert(menu.get("Movie", "FPS override") ~= "ON")           -- the menu entry will print something else
    sleep(2)                                                    -- switching the frame rate takes a while
    assert(menu.get("Movie", "FPS override") == "10.000")       -- it should eventually settle to our requested value
    assert(menu.get("FPS override", "Actual FPS") == "10.000")  -- current FPS value can be read from here
    assert(menu.set("Movie", "FPS override", "OFF"))            -- that was it
    assert(menu.get("Movie", "FPS override") == "OFF")          -- make sure it's turned off

    -- LiveView test completed
    menu.close()
    lv.stop()
    assert(not lv.running)
    assert(not lv.enabled)
    menu.open()

    assert(menu.select("Shoot"))
395
396
397
398
    assert(menu.select("Shoot", "Advanced Bracket"))

    -- boolean items should be set-able as int (0 or 1)
    assert(menu.set("Shoot", "Advanced Bracket", 1))
399
    assert(menu.get("Shoot", "Advanced Bracket", 0) == 1)
400
    sleep(1)
401
402
403
404

    -- 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"))
405
    assert(menu.get("Shoot", "Intervalometer", 0) == 1)
406
    sleep(1)
407
408
409

    -- turning off should be straightforward
    assert(menu.set("Shoot", "Advanced Bracket", "OFF"))
410
411
    assert(menu.get("Shoot", "Advanced Bracket", 0) == 0)
    assert(menu.get("Shoot", "Advanced Bracket") == "OFF")
412
    sleep(1)
413

414
415
416
417
418
419
420
421
    -- "ON" and "OFF" are interpreted as booleans,
    -- even if the menu entry doesn't display exactly this string
    assert(menu.set("Shoot", "Advanced Bracket", "ON"))
    assert(menu.get("Shoot", "Advanced Bracket") ~= "ON")
    assert(menu.get("Shoot", "Advanced Bracket") ~= "OFF")
    assert(menu.set("Shoot", "Advanced Bracket", "OFF"))
    assert(menu.get("Shoot", "Advanced Bracket") == "OFF")

422
    assert(menu.set("Shoot", "Intervalometer", 0))
423
424
    assert(menu.get("Shoot", "Intervalometer", 0) == 0)
    assert(menu.get("Shoot", "Intervalometer") == "OFF")
425
    sleep(1)
426
427
428

    -- move to Intervalometer menu
    assert(menu.select("Shoot", "Intervalometer"))
429
    sleep(1)
430
431

    -- enter the submenu
432
    assert(menu.select("Intervalometer", "Take a pic every"))
433
    sleep(1)
434
435
436

    -- note: setting menu by string works by brute force
    -- that is, trying every possible value and comparing the string
437
438
439
440
441
    assert(menu.set("Intervalometer", "Take a pic every", "1m10s"))
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 70)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m10s")

    -- going from 1m10s to 1m30s should be easy
442
    assert(menu.set("Intervalometer", "Take a pic every", "1m30s"))
443
444
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 90)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m30s")
445
446
447
448
449

    -- going back to 1m10s would have to go through LOTS of intermediate values
    assert(menu.set("Intervalometer", "Take a pic every", "1m10s"))
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 70)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m10s")
450
    sleep(1)
451
452
453

    -- actual string will be 10s
    assert(menu.set("Intervalometer", "Take a pic every", "10"))
454
455
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 10)
    assert(menu.get("Intervalometer", "Take a pic every") == "10s")
456
    sleep(1)
457

458
459
    -- integer should work as well, as long as the internal state variable matches the menu value
    -- on other menus, where internal state is some index, integer argument may not be the best choice
460
    assert(menu.set("Intervalometer", "Take a pic every", 70))
461
462
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 70)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m10s")
463
    sleep(1)
464
465

    -- out of range, should fail
466
    assert(menu.set("Intervalometer", "Take a pic every", 7000000) == false)
467
468
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 70)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m10s")
469
    sleep(1)
470
471

    -- exit submenu
472
    assert(menu.select("Shoot", "Intervalometer"))
473
    sleep(1)
474

475
476
    -- let's try some more submenus
    -- todo: more multi-level submenus
477
478
479
480
481
482
483
484
485
486
    assert(menu.select("Advanced Bracket", "Frames")); sleep(1)
    assert(menu.select("Advanced Bracket", "Sequence")); sleep(1)
    assert(menu.select("Advanced Bracket", "ISO shifting")); sleep(1)
    assert(menu.select("Bulb Timer", "Exposure duration")); sleep(1)
    assert(menu.select("Shoot Preferences", "Snap Simulation")); sleep(1)
    assert(menu.select("Misc key settings", "Sticky HalfShutter")); sleep(1)
    assert(menu.select("Play mode actions", "Trigger key(s)")); sleep(1)
    assert(menu.select("LiveView zoom tweaks", "Zoom on HalfShutter")); sleep(1)
    assert(menu.select("Lens info", "Lens ID")); sleep(1)
    assert(menu.select("Shoot", "Intervalometer")); sleep(1)
487

488
489
490
    -- selection in the Modified menu
    -- note: we have already modified "Take a pic every"
    -- so at least this setting should appear there
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
    assert(menu.set("Shoot", "Intervalometer", "ON")); sleep(1)
    assert(menu.select("Modified", "Intervalometer")); sleep(1)             -- we should find Intervalometer in the Modified menu
    assert(menu.select("Modified", "Take a pic every")); sleep(1)           -- same for "Take pic every" (smaller font)
    assert(menu.select("Modified", "Intervalometer")); sleep(1)             -- scroll back to intervalometer
    assert(menu.set("Shoot", "Intervalometer", "OFF")); sleep(1)            -- turn it off
    assert(menu.select("Modified", "Take a pic every") == false); sleep(1)  -- now "Take a pic every" should disappear (submenus are only expanded when the main entry is on)
    assert(menu.set("Shoot", "Intervalometer", "ON")); sleep(1)             -- re-enable intervalometer
    assert(menu.select("Modified", "Take a pic every")); sleep(1)           -- now "Take a pic every" should re-appear
    assert(menu.set("Intervalometer", "Take a pic every", 10)); sleep(1)    -- set "Take a pic every" back to default
    assert(menu.select("Modified", "Intervalometer")); sleep(1)             -- scroll back to intervalometer
    assert(menu.set("Shoot", "Intervalometer", "OFF")); sleep(1)            -- turn it off
    assert(menu.select("Modified", "Take a pic every") == false); sleep(1)  -- now "Take a pic every" should disappear
    assert(menu.select("Shoot", "Intervalometer")); sleep(1)                -- move back to Shoot -> Intervalometer
    assert(menu.select("Intervalometer", "Take a pic every")); sleep(1)     -- set other intervalometer settings to default (just in case)
    assert(menu.select("Intervalometer", "Start trigger")); sleep(1)        -- the menu.select calls are just for visual effect
    assert(menu.set("Intervalometer", "Start trigger", "Leave Menu")); sleep(0.5);
    assert(menu.select("Intervalometer", "Start after")); sleep(0.5)
    assert(menu.set("Intervalometer", "Start after", "3s")); sleep(0.5);
    assert(menu.select("Intervalometer", "Stop after")); sleep(0.5)
    assert(menu.set("Intervalometer", "Stop after", "Disabled")); sleep(0.5);
    assert(menu.select("Shoot", "Intervalometer")); sleep(1)
    assert(menu.select("Modified", "Intervalometer") == false); sleep(1)    -- now Modified -> Intervalometer should disappear
513

514
    -- non-existent menus; should fail
515
516
517
518
    assert(menu.select("Dinosaur") == false)
    assert(menu.select("Shoot", "Crocodile") == false)

    -- menu.get/set return nil if the menu was not found
519
520
    assert(menu.get("Shoot", "Introvolometer", 0) == nil)
    assert(menu.get("Shoot", "Brack") == nil)
521
522
    assert(menu.set("Shoot", "Introvolometer", 1) == nil)
    assert(menu.set("Shoot", "Introvolometer", "OFF") == nil)
523
524
525
526
527
528

    menu.close()

    -- exercise the menu backend a bit
    for i = 1,5 do
        menu.open()
529
        assert(camera.gui.idle == false)
530
        menu.close()
531
        assert(camera.gui.idle == true)
532
533
534
535
536
537
    end

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

538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
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

556
557
558
559
560
561
562
563
564
565
566
567
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

568
569
570
function test_multitasking()
    printf("Testing multitasking...\n")

571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
    -- 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)
595
596
597
598
599

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

600
601
function test_keys()
    printf("Testing half-shutter...\n")
602
603
    for i = 1,10 do
        -- open Canon menu
604
        camera.gui.menu = true
605
        sleep(1)
606
607
        assert(camera.gui.menu == true)
        assert(camera.gui.idle == false)
608
        key.press(KEY.HALFSHUTTER)
609
        sleep(0.2)
610
611
612
        if key.last ~= KEY.HALFSHUTTER then
            printf("warning: last key not half-shutter, but %d\n", key.last)
        end
613
        sleep(1)
614
        -- half-shutter should close Canon menu
615
616
        assert(camera.gui.menu == false)
        assert(camera.gui.idle == true)
617
        key.press(KEY.UNPRESS_HALFSHUTTER)
618
        sleep(0.2)
619
620
621
        if key.last ~= KEY.UNPRESS_HALFSHUTTER then
            printf("warning: last key not unpress half-shutter, but %d\n", key.last)
        end
622
    end
623
624
625
    printf("Half-shutter test OK.\n")
    
    -- todo: test other key codes? press/unpress events?
alex@thinkpad's avatar
alex@thinkpad committed
626
    printf("\n")
627
628
end

629
function test_camera_exposure()
alex@thinkpad's avatar
alex@thinkpad committed
630
    printf("Testing exposure settings...\n")
631
632
633
634
635
636
    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",
637
638
639
640
641
        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
    )
642
643
644
    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)
645

646
    request_mode(MODE.M, "M")
647
    local old_value = camera.shutter.raw
648
    printf("Setting shutter to random values...\n")
649
    for k = 1,100 do
650
651
        local method = math.random(1,4)
        local d = nil
652
653
        if method == 1 then
            local s = math.random(1,30)
654
655
656
657
658
            if math.random(1,2) == 1 then
                camera.shutter.value = s
            else
                camera.shutter = s
            end
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
            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
678
            printf("Error: shutter delta %s EV\n", d)
679
680
681
682
        end

        -- seconds and ms fields should be consistent
        if math.abs(camera.shutter.value - camera.shutter.ms/1000) > 1e-3 then
683
            printf("Error: shutter %ss != %sms\n", camera.shutter.value, camera.shutter.ms)
684
685
686
        end
        
        -- seconds and apex should be consistent
687
        -- see http://dougkerr.net/Pumpkin/articles/APEX.pdf
688
        local expected_apex = math.log(1/camera.shutter.value,2)
689
        if math.abs(expected_apex - camera.shutter.apex) > 0.01 then
690
            printf("Error: shutter %ss != Tv%s, expected %s\n", camera.shutter.value, camera.shutter.apex, expected_apex)
691
692
693
694
695
        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
696
697
698
699
700
            local current = {}
            current.apex  = camera.shutter.apex
            current.value = camera.shutter.value
            current.ms    = camera.shutter.ms
            current.raw   = camera.shutter.raw
701
702
703
            
            -- 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
704
            if field == "ms" and current.ms < 10 and method ~= 2 then
705
706
707
708
                -- how do you write "continue" in Lua?!
                field = "value"
            end
            
709
            camera.shutter[field] = current[field]
710
            
711
            if camera.shutter.value ~= current.value then
712
                printf("Error: shutter set to %s=%s, got %ss, expected %ss\n", field, current[field], camera.shutter.value, current.value)
713
            end
714
            if camera.shutter.ms ~= current.ms then
715
                printf("Error: shutter set to %s=%s, got %sms, expected %sms\n", field, current[field], camera.shutter.ms, current.ms)
716
            end
717
            if camera.shutter.apex ~= current.apex then
718
                printf("Error: shutter set to %s=%s, got Tv%s, expected Tv%s\n", field, current[field], camera.shutter.apex, current.apex)
719
            end
720
            if camera.shutter.raw ~= current.raw then
721
                printf("Error: shutter set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.shutter.raw, current.raw)
722
723
724
            end
        end
    end
725
    camera.shutter.raw = old_value
726

727
    request_mode(MODE.M, "M")
728
    old_value = camera.iso.raw
729
    printf("Setting ISO to random values...\n")
730
    for k = 1,100 do
731
732
        local method = math.random(1,3)
        local d = nil
733
734
        if method == 1 then
            local iso = math.random(100, 6400)
735
736
737
738
739
            if math.random(1,2) == 1 then
                camera.iso.value = iso
            else
                camera.iso = iso
            end
740
741
742
743
744
745
746
747
748
749
750
751
752
            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
753
            printf("Error: ISO delta %s EV\n", d)
754
755
756
        end

        -- ISO and Sv (APEX) should be consistent
757
        local expected_apex = math.log(camera.iso.value/3.125, 2)
758
        if math.abs(expected_apex - camera.iso.apex) > 0.2 then
759
            printf("Error: ISO %s != Sv%s, expected %s\n", camera.iso.value, camera.iso.apex, expected_apex)
760
761
762
763
764
        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
765
766
767
768
            local current = {}
            current.apex  = camera.iso.apex
            current.value = camera.iso.value
            current.raw   = camera.iso.raw
769
            
770
            camera.iso[field] = current[field]
771
            
772
            if camera.iso.value ~= current.value then
773
                printf("Error: ISO set to %s=%s, got %s, expected %s\n", field, current[field], camera.iso.value, current.value)
774
            end
775
            if camera.iso.apex ~= current.apex then
776
                printf("Error: ISO set to %s=%s, got Sv%s, expected Sv%s\n", field, current[field], camera.iso.apex, current.apex)
777
            end
778
            if camera.iso.raw ~= current.raw then
779
                printf("Error: ISO set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.iso.raw, current.raw)
780
781
782
            end
        end
    end
783
    camera.iso.raw = old_value
784

a1ex's avatar
a1ex committed
785
    if camera.aperture.min.raw == camera.aperture.max.raw then
786
        printf("This lens does not have variable aperture (skipping test).\n")
a1ex's avatar
a1ex committed
787
788
    else
        request_mode(MODE.M, "M")
789
        old_value = camera.aperture.raw
790
        printf("Setting aperture to random values...\n")
a1ex's avatar
a1ex committed
791
        for k = 1,100 do
792
793
794
            local method = math.random(1,3)
            local d = nil
            local extra_tol = 0
a1ex's avatar
a1ex committed
795
796
            if method == 1 then
                local av = math.random(round(camera.aperture.min.value*10), round(camera.aperture.max.value*10)) / 10
797
798
799
800
801
                if math.random(1,2) == 1 then
                    camera.aperture.value = av
                else
                    camera.aperture = av
                end
a1ex's avatar
a1ex committed
802
                d = math.abs(math.log(camera.aperture.value,2) - math.log(av,2)) * 2
803
804
                -- 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
805
806
807
808
809
810
811
812
813
814
815
            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
816
817
818
            -- 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
819
                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
820
821
822
            end

            -- aperture and Av (APEX) should be consistent
823
            local expected_apex = math.log(camera.aperture.value, 2) * 2
a1ex's avatar
a1ex committed
824
            if math.abs(expected_apex - camera.aperture.apex) > 0.2 then
825
                printf("Error: aperture %s != Av%s, expected %s\n", camera.aperture.value, camera.aperture.apex, expected_apex)
a1ex's avatar
a1ex committed
826
827
828
829
830
            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
831
832
833
834
                local current = {}
                current.apex  = camera.aperture.apex
                current.value = camera.aperture.value
                current.raw   = camera.aperture.raw
a1ex's avatar
a1ex committed
835
                
836
                camera.aperture[field] = current[field]
a1ex's avatar
a1ex committed
837
                
838
                if camera.aperture.value ~= current.value then
839
                    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
840
                end
841
                if camera.aperture.apex ~= current.apex then
842
                    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
843
                end
844
                if camera.aperture.raw ~= current.raw then
845
                    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
846
847
848
                end
            end
        end
849
        camera.aperture.raw = old_value
a1ex's avatar
a1ex committed
850
851
    end

852
    request_mode(MODE.AV, "Av")
853
    old_value = camera.ec.raw
854
    printf("Setting EC to random values...\n")
855
    for k = 1,100 do
856
857
        local method = math.random(1,2)
        local d = nil
858
859
        if method == 1 then
            local ec = math.random(-2*100, 2*100) / 100
860
861
862
863
864
            if math.random(1,2) == 1 then
                camera.ec.value = ec
            else
                camera.ec = ec
            end
865
866
867
868
869
870
871
872
873
            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
874
            printf("Error: EC delta %s EV\n", d)
875
876
877
        end

        -- EC and raw should be consistent
878
        local expected_ec = camera.ec.raw / 8
879
        if math.abs(expected_ec - camera.ec.value) > 0.001 then
880
            printf("Error: EC raw %s != %s EV, expected %s EV\n", camera.ec.raw, camera.ec.value, expected_ec)
881
882
883
884
885
        end

        -- setting EC to the same value, using any method (value,raw)
        -- should not change anything
        for i,field in pairs{"value","raw"} do
886
887
888
            local current = {}
            current.value = camera.ec.value
            current.raw   = camera.ec.raw
889
            
890
            camera.ec[field] = current[field]
891
            
892
            if camera.ec.value ~= current.value then
893
                printf("Error: EC set to %s=%s, got %s, expected %s EV\n", field, current[field], camera.ec.value, current.value)
894
            end
895
            if camera.ec.raw ~= current.raw then
896
                printf("Error: EC set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.ec.raw, current.raw)
897
898
899
            end
        end
    end
900
    camera.ec.raw = old_value
901
902

    -- copy/paste & replace from EC (those two should behave in the same way)
903
    old_value = camera.flash_ec.raw
904
    printf("Setting Flash EC to random values...\n")
905
    for k = 1,100 do
906
907
        local method = math.random(1,2)
        local d = nil
908
909
        if method == 1 then
            local fec = math.random(-2*100, 2*100) / 100
910
911
912
913
914
            if math.random(1,2) == 1 then
                camera.flash_ec.value = fec
            else
                camera.flash_ec = fec
            end
915
916
917
918
919
920
921
922
923
            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
924
            printf("Error: FEC delta %s EV\n", d)
925
926
927
        end

        -- EC and raw should be consistent
928
        local expected_fec = camera.flash_ec.raw / 8
929
        if math.abs(expected_fec - camera.flash_ec.value) > 0.001 then
930
            printf("Error: FEC raw %s != %s EV, expected %s EV\n", camera.flash_ec.raw, camera.flash_ec.value, current.expected_fec)
931
932
933
934
935
        end

        -- setting EC to the same value, using any method (value,raw)
        -- should not change anything
        for i,field in pairs{"value","raw"} do
936
937
938
            local current = {}
            current.value = camera.flash_ec.value
            current.raw   = camera.flash_ec.raw
939
            
940
            camera.flash_ec[field] = current[field]
941
            
942
            if camera.flash_ec.value ~= current.value then
943
                printf("Error: FEC set to %s=%s, got %s, expected %s EV\n", field, current[field], camera.flash_ec.value, current.value)
944
            end
945
            if camera.flash_ec.raw ~= current.raw then
946
                printf("Error: FEC set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.flash_ec.raw, current.raw)
947
948
949
            end
        end
    end
950
    camera.flash_ec.raw = old_value
951

952
953
    printf("Exposure tests completed.\n")
    printf("\n")
954
955
end

956
957
958
959
960
961
962
963
964
965
966
967
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

968
function test_camera_take_pics()
alex@thinkpad's avatar
alex@thinkpad committed
969
    printf("Testing picture taking functions...\n")
970
971
972
973
    local initial_file_num

    request_mode(MODE.M, "M")
    camera.shutter = 1/50
974
    sleep(2)
975
976
977
978
979
980
981
982
    
    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))

983
    sleep(2)
984
985

    printf("Single picture...\n")
986
987
    -- 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
988
    initial_file_num = dryos.shooting_card.file_number
989
990
991
992
993
994
995
    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)

996
    camera.shoot()
997
998

    -- but either CR2 or JPG should be there afterwards (or maybe both)
999
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 1)
1000
1001
1002
1003
1004
    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)

1005
1006
1007
1008
1009
1010
1011
    -- let's review it
    assert(camera.gui.qr or camera.gui.idle)
    camera.gui.play = true
    assert(camera.gui.play == true)
    assert(camera.gui.play_photo == true)
    assert(camera.gui.play_movie == false)

1012
    sleep(2)
1013
1014
1015
1016

    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
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032

    -- 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)

1033
    camera.burst(2)
1034

1035
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 2)
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
    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
1075

1076
    sleep(2)
1077
1078
1079
1080

    printf("Bulb picture...\n")
    local t0 = dryos.ms_clock
    initial_file_num = dryos.shooting_card.file_number
1081
1082
1083
1084
1085
1086
    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)

1087
    camera.bulb(10)
1088

1089
1090
1091
1092
    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
1093
1094
    -- slow cards may be an issue, so let's allow a wide error margin
    assert(elapsed > 9900 and elapsed < 30000)
1095
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 1)
1096
1097
1098
1099
    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)
1100
1101

    printf("Picture taking tests completed.\n")
alex@thinkpad's avatar
alex@thinkpad committed
1102
    printf("\n")
1103
    sleep(5)
1104
1105
end

1106
function test_lv()
1107
1108
    request_mode(MODE.M, "M")

1109
    printf("Testing module 'lv'...\n")
1110
    if lv.enabled then
1111
        printf("LiveView is running; stopping...\n")
1112
1113
        lv.stop()
        assert(not lv.enabled, "LiveView did not stop")
1114
        sleep(2)
1115
1116
    end

1117
1118
1119
    assert(not lv.enabled)
    assert(lv.vidmode == "PH-NOLV")

1120
    printf("Starting LiveView...\n")
alex@thinkpad's avatar