Read about our upcoming Code of Conduct on this issue

This instance was upgraded to Heptapod 0.28.0 today

api_test.lua 53.7 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
function request_mode(mode, mode_str)
25
    if camera.mode ~= mode or not camera.gui.idle then
26
        printf("Please switch to %s mode.\n", mode_str, mode)
27
28

        while camera.mode ~= mode or not camera.gui.idle do
alex@thinkpad's avatar
alex@thinkpad committed
29
            console.show(); assert(console.visible)
30
            if camera.gui.idle then alert() end
31
            sleep(1)
32
33
        end
    end
34
    sleep(2)
35
36
end

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

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

52
53
54
55
-- used for testing Lua 'strict' mode
declared_var = nil

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

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

72
73
    printf("Strict mode tests passed.\n")
    printf("\n")
74
75
76
end

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

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

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

149
    printf("Copy test OK\n")
150
151
152
end

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

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

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

189
    printf("Append test OK\n")
190
191
end

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
228
229
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

230
231
232
233
234
function card_test()
    if dryos.cf_card then
        printf("CF card (%s) present\n", dryos.cf_card.path)
        printf("- free space: %d MiB\n", dryos.cf_card.free_space)
        printf("- next image: %s\n", dryos.cf_card:image_path(1))
235
        printf("- DCIM dir. : %s\n", dryos.cf_card.dcim_dir.path)
236
237
238
239
240
241
242
        assert(dryos.cf_card.path == "A:/")
        assert(dryos.cf_card.type == "CF")
    end
    if dryos.sd_card then
        printf("SD card (%s) present\n", dryos.sd_card.path)
        printf("- free space: %d MiB\n", dryos.sd_card.free_space)
        printf("- next image: %s\n", dryos.sd_card:image_path(1))
243
        printf("- DCIM dir. : %s\n", dryos.sd_card.dcim_dir.path)
244
245
246
247
248
        assert(dryos.sd_card.path == "B:/")
        assert(dryos.sd_card.type == "SD")
    end
end

249
function test_io()
alex@thinkpad's avatar
alex@thinkpad committed
250
    printf("Testing file I/O...\n")
251
252
253
    stdio_test()
    copy_test("autoexec.bin", "tmp.bin")
    append_test("tmp.txt")
254
255
    rename_test("apple.txt", "banana.txt")
    rename_test("apple.txt", "ML/banana.txt")
256
    card_test()
alex@thinkpad's avatar
alex@thinkpad committed
257
258
259

    printf("File I/O tests completed.\n")
    printf("\n")
260
261
end

262
263
264
265
266
function test_camera_gui()
    printf("Testing Canon GUI functions...\n")

    camera.gui.play = true
    assert(camera.gui.play == true)
267
    assert(camera.gui.mode == 1)
268
269
270

    -- half-shutter should exit playback mode
    key.press(KEY.HALFSHUTTER)
271
    sleep(1)
272
    assert(camera.gui.play == false)
273
    assert(camera.gui.mode == 0)
274
275
276
277
278
279
280
281
282
283
284
285
286
    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)
287
                assert(camera.gui.mode == 1)
288
289
290
291
292
293
            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)
294
                assert(camera.gui.mode == 0)
295
296
297
298
299
300
301
302
            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)
303
                assert(camera.gui.mode == 2)
304
305
306
307
308
309
            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)
310
                assert(camera.gui.mode == 0)
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
            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()
338
    assert(not lv.enabled)
339
340
341
342
343

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

344
function test_menu()
345
    printf("Testing ML menu API...\n")
346
347
348
349
350
351
352
353

    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)
354
    sleep(1)
355
356
357
358

    -- 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)
359
    sleep(1)
360
361
362
363
364
365
366

    -- 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"))
367
    assert(menu.get("Expo", "Picture Style") == "Portrait")
368
    sleep(1)
369
    assert(menu.set("Expo", "Picture Style", 5)) -- OK, selects Neutral
370
    -- skip string test, as other camera models may use different indices
371
    sleep(1)
372
    assert(menu.set("Expo", "Picture Style", "Landscape"))
373
    assert(menu.get("Expo", "Picture Style") == "Landscape")
374
    sleep(1)
375
    assert(menu.set("Expo", "Picture Style", 1234) == false) -- should fail, out of range
376
    assert(menu.get("Expo", "Picture Style") == "Landscape") -- old selection should not change
377
    sleep(1)
378
379

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

381
382
    assert(menu.select("Movie"))

383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
    -- 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"))
417
418
419
420
    assert(menu.select("Shoot", "Advanced Bracket"))

    -- boolean items should be set-able as int (0 or 1)
    assert(menu.set("Shoot", "Advanced Bracket", 1))
421
    assert(menu.get("Shoot", "Advanced Bracket", 0) == 1)
422
    sleep(1)
423
424
425
426

    -- 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"))
427
    assert(menu.get("Shoot", "Intervalometer", 0) == 1)
428
    sleep(1)
429
430
431

    -- turning off should be straightforward
    assert(menu.set("Shoot", "Advanced Bracket", "OFF"))
432
433
    assert(menu.get("Shoot", "Advanced Bracket", 0) == 0)
    assert(menu.get("Shoot", "Advanced Bracket") == "OFF")
434
    sleep(1)
435

436
437
438
439
440
441
442
443
    -- "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")

444
    assert(menu.set("Shoot", "Intervalometer", 0))
445
446
    assert(menu.get("Shoot", "Intervalometer", 0) == 0)
    assert(menu.get("Shoot", "Intervalometer") == "OFF")
447
    sleep(1)
448
449
450

    -- move to Intervalometer menu
    assert(menu.select("Shoot", "Intervalometer"))
451
    sleep(1)
452
453

    -- enter the submenu
454
    assert(menu.select("Intervalometer", "Take a pic every"))
455
    sleep(1)
456
457
458

    -- note: setting menu by string works by brute force
    -- that is, trying every possible value and comparing the string
459
460
461
462
463
    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
464
    assert(menu.set("Intervalometer", "Take a pic every", "1m30s"))
465
466
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 90)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m30s")
467
468
469
470
471

    -- 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")
472
    sleep(1)
473
474
475

    -- actual string will be 10s
    assert(menu.set("Intervalometer", "Take a pic every", "10"))
476
477
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 10)
    assert(menu.get("Intervalometer", "Take a pic every") == "10s")
478
    sleep(1)
479

480
481
    -- 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
482
    assert(menu.set("Intervalometer", "Take a pic every", 70))
483
484
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 70)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m10s")
485
    sleep(1)
486
487

    -- out of range, should fail
488
    assert(menu.set("Intervalometer", "Take a pic every", 7000000) == false)
489
490
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 70)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m10s")
491
    sleep(1)
492
493

    -- exit submenu
494
    assert(menu.select("Shoot", "Intervalometer"))
495
    sleep(1)
496

497
498
    -- let's try some more submenus
    -- todo: more multi-level submenus
499
500
501
502
503
504
505
506
507
508
    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)
509

510
511
512
    -- selection in the Modified menu
    -- note: we have already modified "Take a pic every"
    -- so at least this setting should appear there
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
    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
535

536
    -- non-existent menus; should fail
537
538
539
540
    assert(menu.select("Dinosaur") == false)
    assert(menu.select("Shoot", "Crocodile") == false)

    -- menu.get/set return nil if the menu was not found
541
542
    assert(menu.get("Shoot", "Introvolometer", 0) == nil)
    assert(menu.get("Shoot", "Brack") == nil)
543
544
    assert(menu.set("Shoot", "Introvolometer", 1) == nil)
    assert(menu.set("Shoot", "Introvolometer", "OFF") == nil)
545
546
547
548
549
550

    menu.close()

    -- exercise the menu backend a bit
    for i = 1,5 do
        menu.open()
551
        assert(camera.gui.idle == false)
552
        menu.close()
553
        assert(camera.gui.idle == true)
554
555
556
557
558
559
    end

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

560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
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

578
579
580
581
582
583
584
585
586
587
588
589
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

590
591
592
function test_multitasking()
    printf("Testing multitasking...\n")

593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
    -- 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)
617
618
619
620
621

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

622
623
function test_keys()
    printf("Testing half-shutter...\n")
624
625
    for i = 1,10 do
        -- open Canon menu
626
        camera.gui.menu = true
627
        sleep(1)
628
629
        assert(camera.gui.menu == true)
        assert(camera.gui.idle == false)
630
        key.press(KEY.HALFSHUTTER)
631
        sleep(0.2)
632
633
634
        if key.last ~= KEY.HALFSHUTTER then
            printf("warning: last key not half-shutter, but %d\n", key.last)
        end
635
        sleep(1)
636
        -- half-shutter should close Canon menu
637
638
        assert(camera.gui.menu == false)
        assert(camera.gui.idle == true)
639
        key.press(KEY.UNPRESS_HALFSHUTTER)
640
        sleep(0.2)
641
642
643
        if key.last ~= KEY.UNPRESS_HALFSHUTTER then
            printf("warning: last key not unpress half-shutter, but %d\n", key.last)
        end
644
    end
645
646
647
    printf("Half-shutter test OK.\n")
    
    -- todo: test other key codes? press/unpress events?
alex@thinkpad's avatar
alex@thinkpad committed
648
    printf("\n")
649
650
end

651
function test_camera_exposure()
alex@thinkpad's avatar
alex@thinkpad committed
652
    printf("Testing exposure settings...\n")
653
654
655
656
657
658
    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",
659
660
661
662
663
        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
    )
664
665
666
    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)
667

668
    request_mode(MODE.M, "M")
669
    local old_value = camera.shutter.raw
670
    printf("Setting shutter to random values...\n")
671
    for k = 1,100 do
672
673
        local method = math.random(1,4)
        local d = nil
674
675
        if method == 1 then
            local s = math.random(1,30)
676
677
678
679
680
            if math.random(1,2) == 1 then
                camera.shutter.value = s
            else
                camera.shutter = s
            end
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
            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
700
            printf("Error: shutter delta %s EV\n", d)
701
702
703
704
        end

        -- seconds and ms fields should be consistent
        if math.abs(camera.shutter.value - camera.shutter.ms/1000) > 1e-3 then
705
            printf("Error: shutter %ss != %sms\n", camera.shutter.value, camera.shutter.ms)
706
707
708
        end
        
        -- seconds and apex should be consistent
709
        -- see http://dougkerr.net/Pumpkin/articles/APEX.pdf
710
        local expected_apex = math.log(1/camera.shutter.value,2)
711
        if math.abs(expected_apex - camera.shutter.apex) > 0.01 then
712
            printf("Error: shutter %ss != Tv%s, expected %s\n", camera.shutter.value, camera.shutter.apex, expected_apex)
713
714
715
716
717
        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
718
719
720
721
722
            local current = {}
            current.apex  = camera.shutter.apex
            current.value = camera.shutter.value
            current.ms    = camera.shutter.ms
            current.raw   = camera.shutter.raw
723
724
725
            
            -- 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
726
            if field == "ms" and current.ms < 10 and method ~= 2 then
727
728
729
730
                -- how do you write "continue" in Lua?!
                field = "value"
            end
            
731
            camera.shutter[field] = current[field]
732
            
733
            if camera.shutter.value ~= current.value then
734
                printf("Error: shutter set to %s=%s, got %ss, expected %ss\n", field, current[field], camera.shutter.value, current.value)
735
            end
736
            if camera.shutter.ms ~= current.ms then
737
                printf("Error: shutter set to %s=%s, got %sms, expected %sms\n", field, current[field], camera.shutter.ms, current.ms)
738
            end
739
            if camera.shutter.apex ~= current.apex then
740
                printf("Error: shutter set to %s=%s, got Tv%s, expected Tv%s\n", field, current[field], camera.shutter.apex, current.apex)
741
            end
742
            if camera.shutter.raw ~= current.raw then
743
                printf("Error: shutter set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.shutter.raw, current.raw)
744
745
746
            end
        end
    end
747
    camera.shutter.raw = old_value
748

749
    request_mode(MODE.M, "M")
750
    old_value = camera.iso.raw
751
    printf("Setting ISO to random values...\n")
752
    for k = 1,100 do
753
754
        local method = math.random(1,3)
        local d = nil
755
        -- 500D: max ISO 3200
756
        if method == 1 then
757
            local iso = math.random(100, 3200)
758
759
760
761
762
            if math.random(1,2) == 1 then
                camera.iso.value = iso
            else
                camera.iso = iso
            end
763
764
            d = math.abs(math.log(camera.iso.value,2) - math.log(iso,2))
        elseif method == 2 then
765
            local apex = math.random(5*100,10*100)/100
766
767
768
            camera.iso.apex = apex
            d = math.abs(camera.iso.apex - apex)
        elseif method == 3 then
769
            local raw = math.random(72, 112)
770
771
772
773
774
775
            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
776
            printf("Error: ISO delta %s EV\n", d)
777
778
779
        end

        -- ISO and Sv (APEX) should be consistent
780
        local expected_apex = math.log(camera.iso.value/3.125, 2)
781
        if math.abs(expected_apex - camera.iso.apex) > 0.2 then
782
            printf("Error: ISO %s != Sv%s, expected %s\n", camera.iso.value, camera.iso.apex, expected_apex)
783
784
785
786
787
        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
788
789
790
791
            local current = {}
            current.apex  = camera.iso.apex
            current.value = camera.iso.value
            current.raw   = camera.iso.raw
792
            
793
            camera.iso[field] = current[field]
794
            
795
            if camera.iso.value ~= current.value then
796
                printf("Error: ISO set to %s=%s, got %s, expected %s\n", field, current[field], camera.iso.value, current.value)
797
            end
798
            if camera.iso.apex ~= current.apex then
799
                printf("Error: ISO set to %s=%s, got Sv%s, expected Sv%s\n", field, current[field], camera.iso.apex, current.apex)
800
            end
801
            if camera.iso.raw ~= current.raw then
802
                printf("Error: ISO set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.iso.raw, current.raw)
803
804
805
            end
        end
    end
806
    camera.iso.raw = old_value
807

a1ex's avatar
a1ex committed
808
    if camera.aperture.min.raw == camera.aperture.max.raw then
809
        printf("This lens does not have variable aperture (skipping test).\n")
a1ex's avatar
a1ex committed
810
811
    else
        request_mode(MODE.M, "M")
812
        old_value = camera.aperture.raw
813
        printf("Setting aperture to random values...\n")
a1ex's avatar
a1ex committed
814
        for k = 1,100 do
815
816
817
            local method = math.random(1,3)
            local d = nil
            local extra_tol = 0
a1ex's avatar
a1ex committed
818
819
            if method == 1 then
                local av = math.random(round(camera.aperture.min.value*10), round(camera.aperture.max.value*10)) / 10
820
821
822
823
824
                if math.random(1,2) == 1 then
                    camera.aperture.value = av
                else
                    camera.aperture = av
                end
a1ex's avatar
a1ex committed
825
                d = math.abs(math.log(camera.aperture.value,2) - math.log(av,2)) * 2
826
827
                -- 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
828
829
830
831
832
833
834
835
836
837
838
            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
839
840
841
            -- 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
842
                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
843
844
845
            end

            -- aperture and Av (APEX) should be consistent
846
            local expected_apex = math.log(camera.aperture.value, 2) * 2
a1ex's avatar
a1ex committed
847
            if math.abs(expected_apex - camera.aperture.apex) > 0.2 then
848
                printf("Error: aperture %s != Av%s, expected %s\n", camera.aperture.value, camera.aperture.apex, expected_apex)
a1ex's avatar
a1ex committed
849
850
851
852
853
            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
854
855
856
857
                local current = {}
                current.apex  = camera.aperture.apex
                current.value = camera.aperture.value
                current.raw   = camera.aperture.raw
a1ex's avatar
a1ex committed
858
                
859
                camera.aperture[field] = current[field]
a1ex's avatar
a1ex committed
860
                
861
                if camera.aperture.value ~= current.value then
862
                    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
863
                end
864
                if camera.aperture.apex ~= current.apex then
865
                    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
866
                end
867
                if camera.aperture.raw ~= current.raw then
868
                    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
869
870
871
                end
            end
        end
872
        camera.aperture.raw = old_value
a1ex's avatar
a1ex committed
873
874
    end

875
    request_mode(MODE.AV, "Av")
876
    old_value = camera.ec.raw
877
    printf("Setting EC to random values...\n")
878
    for k = 1,100 do
879
880
        local method = math.random(1,2)
        local d = nil
881
882
        if method == 1 then
            local ec = math.random(-2*100, 2*100) / 100
883
884
885
886
887
            if math.random(1,2) == 1 then
                camera.ec.value = ec
            else
                camera.ec = ec
            end
888
889
890
891
892
893
894
895
896
            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
897
            printf("Error: EC delta %s EV\n", d)
898
899
900
        end

        -- EC and raw should be consistent
901
        local expected_ec = camera.ec.raw / 8
902
        if math.abs(expected_ec - camera.ec.value) > 0.001 then
903
            printf("Error: EC raw %s != %s EV, expected %s EV\n", camera.ec.raw, camera.ec.value, expected_ec)
904
905
906
907
908
        end

        -- setting EC to the same value, using any method (value,raw)
        -- should not change anything
        for i,field in pairs{"value","raw"} do
909
910
911
            local current = {}
            current.value = camera.ec.value
            current.raw   = camera.ec.raw
912
            
913
            camera.ec[field] = current[field]
914
            
915
            if camera.ec.value ~= current.value then
916
                printf("Error: EC set to %s=%s, got %s, expected %s EV\n", field, current[field], camera.ec.value, current.value)
917
            end
918
            if camera.ec.raw ~= current.raw then
919
                printf("Error: EC set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.ec.raw, current.raw)
920
921
922
            end
        end
    end
923
    camera.ec.raw = old_value
924
925

    -- copy/paste & replace from EC (those two should behave in the same way)
926
    old_value = camera.flash_ec.raw
927
    printf("Setting Flash EC to random values...\n")
928
    for k = 1,100 do
929
930
        local method = math.random(1,2)
        local d = nil
931
932
        if method == 1 then
            local fec = math.random(-2*100, 2*100) / 100
933
934
935
936
937
            if math.random(1,2) == 1 then
                camera.flash_ec.value = fec
            else
                camera.flash_ec = fec
            end
938
939
940
941
942
943
944
945
946
            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
947
            printf("Error: FEC delta %s EV\n", d)
948
949
950
        end

        -- EC and raw should be consistent
951
        local expected_fec = camera.flash_ec.raw / 8
952
        if math.abs(expected_fec - camera.flash_ec.value) > 0.001 then
953
            printf("Error: FEC raw %s != %s EV, expected %s EV\n", camera.flash_ec.raw, camera.flash_ec.value, current.expected_fec)
954
955
956
957
958
        end

        -- setting EC to the same value, using any method (value,raw)
        -- should not change anything
        for i,field in pairs{"value","raw"} do
959
960
961
            local current = {}
            current.value = camera.flash_ec.value
            current.raw   = camera.flash_ec.raw
962
            
963
            camera.flash_ec[field] = current[field]
964
            
965
            if camera.flash_ec.value ~= current.value then
966
                printf("Error: FEC set to %s=%s, got %s, expected %s EV\n", field, current[field], camera.flash_ec.value, current.value)
967
            end
968
            if camera.flash_ec.raw ~= current.raw then
969
                printf("Error: FEC set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.flash_ec.raw, current.raw)
970
971
972
            end
        end
    end
973
    camera.flash_ec.raw = old_value
974

975
976
    printf("Exposure tests completed.\n")
    printf("\n")
977
978
end

979
980
981
982
983
984
985
986
987
988
989
990
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

991
function test_camera_take_pics()
alex@thinkpad's avatar
alex@thinkpad committed
992
    printf("Testing picture taking functions...\n")
993
994
995

    request_mode(MODE.M, "M")
    camera.shutter = 1/50
996
    sleep(2)
997
998
999
    
    printf("Snap simulation test...\n")
    assert(menu.set("Shoot Preferences", "Snap Simulation", 1))
1000
    local initial_file_num = dryos.shooting_card.file_number
1001
1002
1003
1004
    camera.shoot()
    assert(dryos.shooting_card.file_number == initial_file_num)
    assert(menu.set("Shoot Preferences", "Snap Simulation", 0))

1005
    sleep(2)
1006
1007

    printf("Single picture...\n")
1008
    -- let's also check if we can find the image file
1009
    initial_file_num = dryos.shooting_card.file_number
1010
1011
1012
1013
1014
1015
1016
1017
    local image_path_cr2 = dryos.shooting_card:image_path(1, ".CR2") -- next image (assume CR2)
    local image_path_jpg = dryos.shooting_card:image_path(1, ".JPG") -- next image (assume JPG)
    local image_path_auto = dryos.shooting_card:image_path(1)        -- next image (autodetect extension)
    assert(dryos.shooting_card:image_path(1, nil) == image_path_auto)
    assert(image_path_auto == image_path_cr2 or image_path_auto == image_path_jpg)
    assert(dryos.shooting_card:image_path(1, "") .. ".CR2" == image_path_cr2)
    assert(dryos.shooting_card:image_path(1, "") .. ".JPG" == image_path_jpg)

1018
1019
1020
1021
    -- 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)

1022
    camera.shoot()
1023

1024
1025
1026
1027
1028
1029
1030
    assert(dryos.shooting_card:image_path(0) == image_path_auto) -- last captured image
    assert(dryos.shooting_card:image_path() == image_path_auto)  -- that's the default

    -- manually build last image path name
    -- next image path is harder to build manually, as you need to take care
    -- of wrapping around (100CANON/IMG_9999.CR2 -> 101CANON/IMG_0001.CR2)
    -- this is why dryos.shooting_card:image_path is preferred -- it handles these edge cases for you
1031
1032
1033
1034
    local image_path_dcim =
        dryos.shooting_card.dcim_dir.path ..
        dryos.image_prefix ..
        string.format("%04d", dryos.shooting_card.file_number)
1035
1036
    assert(image_path_dcim == dryos.shooting_card:image_path(0, ""))

1037
    -- but either CR2 or JPG should be there afterwards (or maybe both)
1038
    assert((dryos.shooting_card.file_number - initial_file_num) % 9999 == 1)
1039
1040
1041
1042
1043
    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)

1044
1045
1046
1047
1048
1049
1050
    -- 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)

1051
    sleep(2)
1052
1053
1054
1055

    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
1056
1057
1058
1059
1060

    -- 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_")
1061
1062
1063
1064
1065
1066
    -- edge case if image number is 9997 (next image 9998) before running api_test.lua
    -- (image1 will be 100CANON/IMG_9999 and image2 should be 101CANON/IMG_0001)
    local image1_path_cr2 = dryos.shooting_card:image_path(1, ".CR2")
    local image1_path_jpg = dryos.shooting_card:image_path(1, ".JPG")
    local image2_path_cr2 = dryos.shooting_card:image_path(2, ".CR2")
    local image2_path_jpg = dryos.shooting_card:image_path(2, ".JPG")
1067
1068
1069
1070
1071
    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)

1072
    camera.burst(2)
1073

1074
    assert((dryos.shooting_card.file_number - initial_file_num) % 9999 == 2)
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
    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)

1095
1096
1097
1098
1099
    -- edge case if image number is 9994/9995 (next image 9995/9996) before running api_test.lua
    -- images from previous tests will be either (9995, 9996, 9997), or (9996, 9997, 9998)
    -- the next 3 images will be either (100CANON/IMG_9998, 100CANON/IMG_9999, 101CANON/IMG_0001)
    -- or (100CANON/IMG_9999, 101CANON/IMG_0001, 101CANON/IMG_0002)
    -- dryos.shooting_card:image_path takes care of this
1100
1101
1102
1103
1104
1105
1106
1107
    printf("Bracketed pictures...\n")
    initial_file_num = dryos.shooting_card.file_number
    camera.shutter.value = 1/500
    camera.shoot()
    camera.shutter.value = 1/50