Read about our upcoming Code of Conduct on this issue

api_test.lua 47.2 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
            sleep(1)
24
25
26
27
        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
function test_camera_gui()
    printf("Testing Canon GUI functions...\n")

    camera.gui.play = true
    assert(camera.gui.play == true)
238
    assert(camera.gui.mode == 1)
239
240
241

    -- half-shutter should exit playback mode
    key.press(KEY.HALFSHUTTER)
242
    sleep(1)
243
    assert(camera.gui.play == false)
244
    assert(camera.gui.mode == 0)
245
246
247
248
249
250
251
252
253
254
255
256
257
    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)
258
                assert(camera.gui.mode == 1)
259
260
261
262
263
264
            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)
265
                assert(camera.gui.mode == 0)
266
267
268
269
270
271
272
273
            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)
274
                assert(camera.gui.mode == 2)
275
276
277
278
279
280
            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)
281
                assert(camera.gui.mode == 0)
282
283
284
285
286
287
288
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
            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()

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

314
function test_menu()
315
    printf("Testing ML menu API...\n")
316
317
318
319
320
321
322
323

    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)
324
    sleep(1)
325
326
327
328

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

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

    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))
357
    assert(menu.get("Shoot", "Advanced Bracket", 0) == 1)
358
    sleep(1)
359
360
361
362

    -- 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"))
363
    assert(menu.get("Shoot", "Intervalometer", 0) == 1)
364
    sleep(1)
365
366
367

    -- turning off should be straightforward
    assert(menu.set("Shoot", "Advanced Bracket", "OFF"))
368
369
    assert(menu.get("Shoot", "Advanced Bracket", 0) == 0)
    assert(menu.get("Shoot", "Advanced Bracket") == "OFF")
370
    sleep(1)
371
372

    assert(menu.set("Shoot", "Intervalometer", 0))
373
374
    assert(menu.get("Shoot", "Intervalometer", 0) == 0)
    assert(menu.get("Shoot", "Intervalometer") == "OFF")
375
    sleep(1)
376
377
378

    -- move to Intervalometer menu
    assert(menu.select("Shoot", "Intervalometer"))
379
    sleep(1)
380
381

    -- enter the submenu
382
    assert(menu.select("Intervalometer", "Take a pic every"))
383
    sleep(1)
384
385
386
387
388
389
390

    -- 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"))
391
392
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 90)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m30s")
393
    sleep(1)
394
395
396

    -- actual string will be 10s
    assert(menu.set("Intervalometer", "Take a pic every", "10"))
397
398
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 10)
    assert(menu.get("Intervalometer", "Take a pic every") == "10s")
399
    sleep(1)
400
401
402

    -- integer should work as well - e.g. 1m10s should work now
    assert(menu.set("Intervalometer", "Take a pic every", 70))
403
404
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 70)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m10s")
405
    sleep(1)
406
407

    -- out of range, should fail
408
    assert(menu.set("Intervalometer", "Take a pic every", 7000000) == false)
409
410
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 70)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m10s")
411
    sleep(1)
412
413

    -- exit submenu
414
    assert(menu.select("Shoot", "Intervalometer"))
415
    sleep(1)
416

417
418
    -- let's try some more submenus
    -- todo: more multi-level submenus
419
420
421
422
423
424
425
426
427
428
    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)
429

430
431
432
    -- selection in the Modified menu
    -- note: we have already modified "Take a pic every"
    -- so at least this setting should appear there
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
    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
455

456
    -- non-existent menus; should fail
457
458
459
460
    assert(menu.select("Dinosaur") == false)
    assert(menu.select("Shoot", "Crocodile") == false)

    -- menu.get/set return nil if the menu was not found
461
462
    assert(menu.get("Shoot", "Introvolometer", 0) == nil)
    assert(menu.get("Shoot", "Brack") == nil)
463
464
    assert(menu.set("Shoot", "Introvolometer", 1) == nil)
    assert(menu.set("Shoot", "Introvolometer", "OFF") == nil)
465
466
467
468
469
470

    menu.close()

    -- exercise the menu backend a bit
    for i = 1,5 do
        menu.open()
471
        assert(camera.gui.idle == false)
472
        menu.close()
473
        assert(camera.gui.idle == true)
474
475
476
477
478
479
    end

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

480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
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

498
499
500
501
502
503
504
505
506
507
508
509
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

510
511
512
function test_multitasking()
    printf("Testing multitasking...\n")

513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
    -- 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)
537
538
539
540
541

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

542
543
function test_keys()
    printf("Testing half-shutter...\n")
544
545
    for i = 1,10 do
        -- open Canon menu
546
        camera.gui.menu = true
547
        sleep(1)
548
549
        assert(camera.gui.menu == true)
        assert(camera.gui.idle == false)
550
        key.press(KEY.HALFSHUTTER)
551
        sleep(0.2)
552
        assert(key.last == KEY.HALFSHUTTER)
553
        sleep(1)
554
        -- half-shutter should close Canon menu
555
556
        assert(camera.gui.menu == false)
        assert(camera.gui.idle == true)
557
        key.press(KEY.UNPRESS_HALFSHUTTER)
558
        sleep(0.2)
559
560
        assert(key.last == KEY.UNPRESS_HALFSHUTTER)
    end
561
562
563
    printf("Half-shutter test OK.\n")
    
    -- todo: test other key codes? press/unpress events?
alex@thinkpad's avatar
alex@thinkpad committed
564
    printf("\n")
565
566
end

567
function test_camera_exposure()
alex@thinkpad's avatar
alex@thinkpad committed
568
    printf("Testing exposure settings...\n")
569
570
571
572
573
574
    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",
575
576
577
578
579
        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
    )
580
581
582
    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)
583

584
    request_mode(MODE.M, "M")
585
    local old_value = camera.shutter.raw
586
    printf("Setting shutter to random values...\n")
587
    for k = 1,100 do
588
589
        local method = math.random(1,4)
        local d = nil
590
591
        if method == 1 then
            local s = math.random(1,30)
592
593
594
595
596
            if math.random(1,2) == 1 then
                camera.shutter.value = s
            else
                camera.shutter = s
            end
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
            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
616
            printf("Error: shutter delta %s EV\n", d)
617
618
619
620
        end

        -- seconds and ms fields should be consistent
        if math.abs(camera.shutter.value - camera.shutter.ms/1000) > 1e-3 then
621
            printf("Error: shutter %ss != %sms\n", camera.shutter.value, camera.shutter.ms)
622
623
624
        end
        
        -- seconds and apex should be consistent
625
        -- see http://dougkerr.net/Pumpkin/articles/APEX.pdf
626
        local expected_apex = math.log(1/camera.shutter.value,2)
627
        if math.abs(expected_apex - camera.shutter.apex) > 0.01 then
628
            printf("Error: shutter %ss != Tv%s, expected %s\n", camera.shutter.value, camera.shutter.apex, expected_apex)
629
630
631
632
633
        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
634
635
636
637
638
            local current = {}
            current.apex  = camera.shutter.apex
            current.value = camera.shutter.value
            current.ms    = camera.shutter.ms
            current.raw   = camera.shutter.raw
639
640
641
            
            -- 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
642
            if field == "ms" and current.ms < 10 and method ~= 2 then
643
644
645
646
                -- how do you write "continue" in Lua?!
                field = "value"
            end
            
647
            camera.shutter[field] = current[field]
648
            
649
            if camera.shutter.value ~= current.value then
650
                printf("Error: shutter set to %s=%s, got %ss, expected %ss\n", field, current[field], camera.shutter.value, current.value)
651
            end
652
            if camera.shutter.ms ~= current.ms then
653
                printf("Error: shutter set to %s=%s, got %sms, expected %sms\n", field, current[field], camera.shutter.ms, current.ms)
654
            end
655
            if camera.shutter.apex ~= current.apex then
656
                printf("Error: shutter set to %s=%s, got Tv%s, expected Tv%s\n", field, current[field], camera.shutter.apex, current.apex)
657
            end
658
            if camera.shutter.raw ~= current.raw then
659
                printf("Error: shutter set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.shutter.raw, current.raw)
660
661
662
            end
        end
    end
663
    camera.shutter.raw = old_value
664

665
    request_mode(MODE.M, "M")
666
    old_value = camera.iso.raw
667
    printf("Setting ISO to random values...\n")
668
    for k = 1,100 do
669
670
        local method = math.random(1,3)
        local d = nil
671
672
        if method == 1 then
            local iso = math.random(100, 6400)
673
674
675
676
677
            if math.random(1,2) == 1 then
                camera.iso.value = iso
            else
                camera.iso = iso
            end
678
679
680
681
682
683
684
685
686
687
688
689
690
            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
691
            printf("Error: ISO delta %s EV\n", d)
692
693
694
        end

        -- ISO and Sv (APEX) should be consistent
695
        local expected_apex = math.log(camera.iso.value/3.125, 2)
696
        if math.abs(expected_apex - camera.iso.apex) > 0.2 then
697
            printf("Error: ISO %s != Sv%s, expected %s\n", camera.iso.value, camera.iso.apex, expected_apex)
698
699
700
701
702
        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
703
704
705
706
            local current = {}
            current.apex  = camera.iso.apex
            current.value = camera.iso.value
            current.raw   = camera.iso.raw
707
            
708
            camera.iso[field] = current[field]
709
            
710
            if camera.iso.value ~= current.value then
711
                printf("Error: ISO set to %s=%s, got %s, expected %s\n", field, current[field], camera.iso.value, current.value)
712
            end
713
            if camera.iso.apex ~= current.apex then
714
                printf("Error: ISO set to %s=%s, got Sv%s, expected Sv%s\n", field, current[field], camera.iso.apex, current.apex)
715
            end
716
            if camera.iso.raw ~= current.raw then
717
                printf("Error: ISO set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.iso.raw, current.raw)
718
719
720
            end
        end
    end
721
    camera.iso.raw = old_value
722

a1ex's avatar
a1ex committed
723
    if camera.aperture.min.raw == camera.aperture.max.raw then
724
        printf("This lens does not have variable aperture (skipping test).\n")
a1ex's avatar
a1ex committed
725
726
    else
        request_mode(MODE.M, "M")
727
        old_value = camera.aperture.raw
728
        printf("Setting aperture to random values...\n")
a1ex's avatar
a1ex committed
729
        for k = 1,100 do
730
731
732
            local method = math.random(1,3)
            local d = nil
            local extra_tol = 0
a1ex's avatar
a1ex committed
733
734
            if method == 1 then
                local av = math.random(round(camera.aperture.min.value*10), round(camera.aperture.max.value*10)) / 10
735
736
737
738
739
                if math.random(1,2) == 1 then
                    camera.aperture.value = av
                else
                    camera.aperture = av
                end
a1ex's avatar
a1ex committed
740
                d = math.abs(math.log(camera.aperture.value,2) - math.log(av,2)) * 2
741
742
                -- 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
743
744
745
746
747
748
749
750
751
752
753
            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
754
755
756
            -- 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
757
                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
758
759
760
            end

            -- aperture and Av (APEX) should be consistent
761
            local expected_apex = math.log(camera.aperture.value, 2) * 2
a1ex's avatar
a1ex committed
762
            if math.abs(expected_apex - camera.aperture.apex) > 0.2 then
763
                printf("Error: aperture %s != Av%s, expected %s\n", camera.aperture.value, camera.aperture.apex, expected_apex)
a1ex's avatar
a1ex committed
764
765
766
767
768
            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
769
770
771
772
                local current = {}
                current.apex  = camera.aperture.apex
                current.value = camera.aperture.value
                current.raw   = camera.aperture.raw
a1ex's avatar
a1ex committed
773
                
774
                camera.aperture[field] = current[field]
a1ex's avatar
a1ex committed
775
                
776
                if camera.aperture.value ~= current.value then
777
                    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
778
                end
779
                if camera.aperture.apex ~= current.apex then
780
                    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
781
                end
782
                if camera.aperture.raw ~= current.raw then
783
                    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
784
785
786
                end
            end
        end
787
        camera.aperture.raw = old_value
a1ex's avatar
a1ex committed
788
789
    end

790
    request_mode(MODE.AV, "Av")
791
    old_value = camera.ec.raw
792
    printf("Setting EC to random values...\n")
793
    for k = 1,100 do
794
795
        local method = math.random(1,2)
        local d = nil
796
797
        if method == 1 then
            local ec = math.random(-2*100, 2*100) / 100
798
799
800
801
802
            if math.random(1,2) == 1 then
                camera.ec.value = ec
            else
                camera.ec = ec
            end
803
804
805
806
807
808
809
810
811
            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
812
            printf("Error: EC delta %s EV\n", d)
813
814
815
        end

        -- EC and raw should be consistent
816
        local expected_ec = camera.ec.raw / 8
817
        if math.abs(expected_ec - camera.ec.value) > 0.001 then
818
            printf("Error: EC raw %s != %s EV, expected %s EV\n", camera.ec.raw, camera.ec.value, expected_ec)
819
820
821
822
823
        end

        -- setting EC to the same value, using any method (value,raw)
        -- should not change anything
        for i,field in pairs{"value","raw"} do
824
825
826
            local current = {}
            current.value = camera.ec.value
            current.raw   = camera.ec.raw
827
            
828
            camera.ec[field] = current[field]
829
            
830
            if camera.ec.value ~= current.value then
831
                printf("Error: EC set to %s=%s, got %s, expected %s EV\n", field, current[field], camera.ec.value, current.value)
832
            end
833
            if camera.ec.raw ~= current.raw then
834
                printf("Error: EC set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.ec.raw, current.raw)
835
836
837
            end
        end
    end
838
    camera.ec.raw = old_value
839
840

    -- copy/paste & replace from EC (those two should behave in the same way)
841
    old_value = camera.flash_ec.raw
842
    printf("Setting Flash EC to random values...\n")
843
    for k = 1,100 do
844
845
        local method = math.random(1,2)
        local d = nil
846
847
        if method == 1 then
            local fec = math.random(-2*100, 2*100) / 100
848
849
850
851
852
            if math.random(1,2) == 1 then
                camera.flash_ec.value = fec
            else
                camera.flash_ec = fec
            end
853
854
855
856
857
858
859
860
861
            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
862
            printf("Error: FEC delta %s EV\n", d)
863
864
865
        end

        -- EC and raw should be consistent
866
        local expected_fec = camera.flash_ec.raw / 8
867
        if math.abs(expected_fec - camera.flash_ec.value) > 0.001 then
868
            printf("Error: FEC raw %s != %s EV, expected %s EV\n", camera.flash_ec.raw, camera.flash_ec.value, current.expected_fec)
869
870
871
872
873
        end

        -- setting EC to the same value, using any method (value,raw)
        -- should not change anything
        for i,field in pairs{"value","raw"} do
874
875
876
            local current = {}
            current.value = camera.flash_ec.value
            current.raw   = camera.flash_ec.raw
877
            
878
            camera.flash_ec[field] = current[field]
879
            
880
            if camera.flash_ec.value ~= current.value then
881
                printf("Error: FEC set to %s=%s, got %s, expected %s EV\n", field, current[field], camera.flash_ec.value, current.value)
882
            end
883
            if camera.flash_ec.raw ~= current.raw then
884
                printf("Error: FEC set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.flash_ec.raw, current.raw)
885
886
887
            end
        end
    end
888
    camera.flash_ec.raw = old_value
889

890
891
    printf("Exposure tests completed.\n")
    printf("\n")
892
893
end

894
895
896
897
898
899
900
901
902
903
904
905
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

906
function test_camera_take_pics()
alex@thinkpad's avatar
alex@thinkpad committed
907
    printf("Testing picture taking functions...\n")
908
909
910
911
    local initial_file_num

    request_mode(MODE.M, "M")
    camera.shutter = 1/50
912
    sleep(2)
913
914
915
916
917
918
919
920
    
    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))

921
    sleep(2)
922
923

    printf("Single picture...\n")
924
925
    -- 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
926
    initial_file_num = dryos.shooting_card.file_number
927
928
929
930
931
932
933
    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)

934
    camera.shoot()
935
936

    -- but either CR2 or JPG should be there afterwards (or maybe both)
937
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 1)
938
939
940
941
942
    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)

943
944
945
946
947
948
949
    -- 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)

950
    sleep(2)
951
952
953
954

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

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

971
    camera.burst(2)
972

973
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 2)
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
1004
1005
1006
1007
1008
1009
1010
1011
1012
    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
1013

1014
    sleep(2)
1015
1016
1017
1018

    printf("Bulb picture...\n")
    local t0 = dryos.ms_clock
    initial_file_num = dryos.shooting_card.file_number
1019
1020
1021
1022
1023
1024
    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)

1025
    camera.bulb(10)
1026

1027
1028
1029
1030
    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
1031
1032
    -- slow cards may be an issue, so let's allow a wide error margin
    assert(elapsed > 9900 and elapsed < 30000)
1033
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 1)
1034
1035
1036
1037
    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)
1038
1039

    printf("Picture taking tests completed.\n")
alex@thinkpad's avatar
alex@thinkpad committed
1040
    printf("\n")
1041
    sleep(5)
1042
1043
end

1044
function test_lv()
1045
1046
    request_mode(MODE.M, "M")

1047
    printf("Testing module 'lv'...\n")
1048
    if lv.enabled then
1049
        printf("LiveView is running; stopping...\n")
1050
1051
        lv.stop()
        assert(not lv.enabled, "LiveView did not stop")
1052
        sleep(2)
1053
1054
    end

1055
1056
1057
    assert(not lv.enabled)
    assert(lv.vidmode == "PH-NOLV")

1058
    printf("Starting LiveView...\n")
1059
1060
    lv.start()
    assert(lv.enabled, "LiveView did not start")
1061
    assert(not lens.autofocusing)
1062
    assert(lv.vidmode == "PH-LV")
1063
    assert(camera.gui.idle == true)
1064

1065
    sleep(2)
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077

    -- 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)
1078
1079
        sleep(0.2); print_overlays_status()
        sleep(1)
1080
1081
1082
1083
        if lv.overlays ~= 1 then
            -- Canon overlays disabled?
            -- Enable ML overlays
            assert(menu.set("Overlay", "Global Draw", "ON"))
1084
            sleep(0.2); print_overlays_status()
1085
            assert(lv.overlays == 2)
1086
            sleep(1)
1087
1088
            -- Disable ML overlays
            assert(menu.set("Overlay", "Global Draw", "OFF"))
1089
            sleep(0.2); print_overlays_status()
1090
            assert(lv.overlays == false)
1091
            sleep(1)
1092
1093
1094
1095
1096
        end
    end
    -- restore original Global Draw setting
    assert(menu.set("Overlay", "Global Draw", old_gdr))
    assert(menu.get("Overlay", "Global Draw") == old_gdr)
1097
    sleep(0.2); print_overlays_status()
1098
1099
    console.show(); assert(console.visible)

1100
    sleep(2)
1101

1102
    for i,z in pairs{1, 5, 10, 5, 1, 10, 1} do
1103
        printf("Setting zoom to x%d...\n", z)