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

5
require("logger")
6

7
8
9
-- global logger
test_log = nil

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

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

18
19
20
21
22
23
function alert()
    display.off()
    beep()
    display.on()
end

24
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
235
236
237
238
239
240
241
function list_dir(dir, prefix)
    prefix = prefix or "- "
    local i, d, f
    for i,d in pairs(dir:children()) do
       printf("%s%s\n", prefix, d)
       list_dir(d, "  " .. prefix)
    end
    for i,f in pairs(dir:files()) do
       printf("%s%s\n", prefix, f)
    end
end

242
243
244
245
246
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))
247
        printf("- DCIM dir. : %s\n", dryos.cf_card.dcim_dir)
248
249
        assert(dryos.cf_card.path == "A:/")
        assert(dryos.cf_card.type == "CF")
250
251
        list_dir(dryos.cf_card.dcim_dir)
        list_dir(dryos.directory(dryos.cf_card.path))
252
253
254
255
256
    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))
257
        printf("- DCIM dir. : %s\n", dryos.sd_card.dcim_dir)
258
259
        assert(dryos.sd_card.path == "B:/")
        assert(dryos.sd_card.type == "SD")
260
261
        list_dir(dryos.sd_card.dcim_dir)
        list_dir(dryos.directory(dryos.sd_card.path))
262
263
264
    end
end

265
function test_io()
alex@thinkpad's avatar
alex@thinkpad committed
266
    printf("Testing file I/O...\n")
267
268
269
    stdio_test()
    copy_test("autoexec.bin", "tmp.bin")
    append_test("tmp.txt")
270
271
    rename_test("apple.txt", "banana.txt")
    rename_test("apple.txt", "ML/banana.txt")
272
    card_test()
alex@thinkpad's avatar
alex@thinkpad committed
273
274
275

    printf("File I/O tests completed.\n")
    printf("\n")
276
277
end

278
279
280
281
282
function test_camera_gui()
    printf("Testing Canon GUI functions...\n")

    camera.gui.play = true
    assert(camera.gui.play == true)
283
    assert(camera.gui.mode == 1)
284
285
286

    -- half-shutter should exit playback mode
    key.press(KEY.HALFSHUTTER)
287
    sleep(1)
288
    assert(camera.gui.play == false)
289
    assert(camera.gui.mode == 0)
290
291
292
293
294
295
296
297
298
299
300
301
302
    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)
303
                assert(camera.gui.mode == 1)
304
305
306
307
308
309
            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)
310
                assert(camera.gui.mode == 0)
311
312
313
314
315
316
317
318
            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)
319
                assert(camera.gui.mode == 2)
320
321
322
323
324
325
            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)
326
                assert(camera.gui.mode == 0)
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
            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()
354
    assert(not lv.enabled)
355
356
357
358
359

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

360
function test_menu()
361
    printf("Testing ML menu API...\n")
362
363
364
365
366
367
368
369

    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)
370
    sleep(1)
371
372
373
374

    -- 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)
375
    sleep(1)
376
377
378
379
380
381
382

    -- 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"))
383
    assert(menu.get("Expo", "Picture Style") == "Portrait")
384
    sleep(1)
385
    assert(menu.set("Expo", "Picture Style", 5)) -- OK, selects Neutral
386
    -- skip string test, as other camera models may use different indices
387
    sleep(1)
388
    assert(menu.set("Expo", "Picture Style", "Landscape"))
389
    assert(menu.get("Expo", "Picture Style") == "Landscape")
390
    sleep(1)
391
    assert(menu.set("Expo", "Picture Style", 1234) == false) -- should fail, out of range
392
    assert(menu.get("Expo", "Picture Style") == "Landscape") -- old selection should not change
393
    sleep(1)
394
395

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

397
398
    assert(menu.select("Movie"))

399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
    -- only run this test if FPS override is available in menu
    if menu.get("Movie", "FPS override") ~= nil then
        -- 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()
    end
434
435

    assert(menu.select("Shoot"))
436
437
438
439
    assert(menu.select("Shoot", "Advanced Bracket"))

    -- boolean items should be set-able as int (0 or 1)
    assert(menu.set("Shoot", "Advanced Bracket", 1))
440
    assert(menu.get("Shoot", "Advanced Bracket", 0) == 1)
441
    sleep(1)
442
443
444
445

    -- 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"))
446
    assert(menu.get("Shoot", "Intervalometer", 0) == 1)
447
    sleep(1)
448
449
450

    -- turning off should be straightforward
    assert(menu.set("Shoot", "Advanced Bracket", "OFF"))
451
452
    assert(menu.get("Shoot", "Advanced Bracket", 0) == 0)
    assert(menu.get("Shoot", "Advanced Bracket") == "OFF")
453
    sleep(1)
454

455
456
457
458
459
460
461
462
    -- "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")

463
    assert(menu.set("Shoot", "Intervalometer", 0))
464
465
    assert(menu.get("Shoot", "Intervalometer", 0) == 0)
    assert(menu.get("Shoot", "Intervalometer") == "OFF")
466
    sleep(1)
467
468
469

    -- move to Intervalometer menu
    assert(menu.select("Shoot", "Intervalometer"))
470
    sleep(1)
471
472

    -- enter the submenu
473
    assert(menu.select("Intervalometer", "Take a pic every"))
474
    sleep(1)
475
476
477

    -- note: setting menu by string works by brute force
    -- that is, trying every possible value and comparing the string
478
479
480
481
482
    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
483
    assert(menu.set("Intervalometer", "Take a pic every", "1m30s"))
484
485
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 90)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m30s")
486
487
488
489
490

    -- 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")
491
    sleep(1)
492
493
494

    -- actual string will be 10s
    assert(menu.set("Intervalometer", "Take a pic every", "10"))
495
496
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 10)
    assert(menu.get("Intervalometer", "Take a pic every") == "10s")
497
    sleep(1)
498

499
500
    -- 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
501
    assert(menu.set("Intervalometer", "Take a pic every", 70))
502
503
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 70)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m10s")
504
    sleep(1)
505
506

    -- out of range, should fail
507
    assert(menu.set("Intervalometer", "Take a pic every", 7000000) == false)
508
509
    assert(menu.get("Intervalometer", "Take a pic every", 0) == 70)
    assert(menu.get("Intervalometer", "Take a pic every") == "1m10s")
510
    sleep(1)
511
512

    -- exit submenu
513
    assert(menu.select("Shoot", "Intervalometer"))
514
    sleep(1)
515

516
517
    -- let's try some more submenus
    -- todo: more multi-level submenus
518
519
520
521
522
523
    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)
524
    -- assert(menu.select("Play mode actions", "Trigger key(s)")); sleep(1)     -- FIXME: not present on EOS M
525
526
527
    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)
528

529
530
531
    -- selection in the Modified menu
    -- note: we have already modified "Take a pic every"
    -- so at least this setting should appear there
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
    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
554

555
    -- non-existent menus; should fail
556
557
558
559
    assert(menu.select("Dinosaur") == false)
    assert(menu.select("Shoot", "Crocodile") == false)

    -- menu.get/set return nil if the menu was not found
560
561
    assert(menu.get("Shoot", "Introvolometer", 0) == nil)
    assert(menu.get("Shoot", "Brack") == nil)
562
563
    assert(menu.set("Shoot", "Introvolometer", 1) == nil)
    assert(menu.set("Shoot", "Introvolometer", "OFF") == nil)
564
565
566
567
568
569

    menu.close()

    -- exercise the menu backend a bit
    for i = 1,5 do
        menu.open()
570
        assert(camera.gui.idle == false)
571
        menu.close()
572
        assert(camera.gui.idle == true)
573
574
575
576
577
578
    end

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

579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
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

597
598
599
600
601
602
603
604
605
606
607
608
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

609
610
611
function test_multitasking()
    printf("Testing multitasking...\n")

612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
    -- 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)
636
637
638
639
640

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

641
642
function test_keys()
    printf("Testing half-shutter...\n")
643
644
    for i = 1,10 do
        -- open Canon menu
645
        camera.gui.menu = true
646
        sleep(1)
647
648
        assert(camera.gui.menu == true)
        assert(camera.gui.idle == false)
649
        key.press(KEY.HALFSHUTTER)
650
        sleep(0.2)
651
652
653
        if key.last ~= KEY.HALFSHUTTER then
            printf("warning: last key not half-shutter, but %d\n", key.last)
        end
654
        sleep(1)
655
        -- half-shutter should close Canon menu
656
657
        assert(camera.gui.menu == false)
        assert(camera.gui.idle == true)
658
        key.press(KEY.UNPRESS_HALFSHUTTER)
659
        sleep(0.2)
660
661
662
        if key.last ~= KEY.UNPRESS_HALFSHUTTER then
            printf("warning: last key not unpress half-shutter, but %d\n", key.last)
        end
663
    end
664
665
666
    printf("Half-shutter test OK.\n")
    
    -- todo: test other key codes? press/unpress events?
alex@thinkpad's avatar
alex@thinkpad committed
667
    printf("\n")
668
669
end

670
function test_camera_exposure()
alex@thinkpad's avatar
alex@thinkpad committed
671
    printf("Testing exposure settings...\n")
672
673
674
675
676
677
    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",
678
679
680
681
682
        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
    )
683
684
685
    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)
686

687
    request_mode(MODE.M, "M")
688
    local old_value = camera.shutter.raw
689
    printf("Setting shutter to random values...\n")
690
    for k = 1,100 do
691
692
        local method = math.random(1,4)
        local d = nil
693
694
        if method == 1 then
            local s = math.random(1,30)
695
696
697
698
699
            if math.random(1,2) == 1 then
                camera.shutter.value = s
            else
                camera.shutter = s
            end
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
            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
719
            printf("Error: shutter delta %s EV\n", d)
720
721
722
723
        end

        -- seconds and ms fields should be consistent
        if math.abs(camera.shutter.value - camera.shutter.ms/1000) > 1e-3 then
724
            printf("Error: shutter %ss != %sms\n", camera.shutter.value, camera.shutter.ms)
725
726
727
        end
        
        -- seconds and apex should be consistent
728
        -- see http://dougkerr.net/Pumpkin/articles/APEX.pdf
729
        local expected_apex = math.log(1/camera.shutter.value,2)
730
        if math.abs(expected_apex - camera.shutter.apex) > 0.01 then
731
            printf("Error: shutter %ss != Tv%s, expected %s\n", camera.shutter.value, camera.shutter.apex, expected_apex)
732
733
734
735
736
        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
737
738
739
740
741
            local current = {}
            current.apex  = camera.shutter.apex
            current.value = camera.shutter.value
            current.ms    = camera.shutter.ms
            current.raw   = camera.shutter.raw
742
743
744
            
            -- 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
745
            if field == "ms" and current.ms < 10 and method ~= 2 then
746
747
748
749
                -- how do you write "continue" in Lua?!
                field = "value"
            end
            
750
            camera.shutter[field] = current[field]
751
            
752
            if camera.shutter.value ~= current.value then
753
                printf("Error: shutter set to %s=%s, got %ss, expected %ss\n", field, current[field], camera.shutter.value, current.value)
754
            end
755
            if camera.shutter.ms ~= current.ms then
756
                printf("Error: shutter set to %s=%s, got %sms, expected %sms\n", field, current[field], camera.shutter.ms, current.ms)
757
            end
758
            if camera.shutter.apex ~= current.apex then
759
                printf("Error: shutter set to %s=%s, got Tv%s, expected Tv%s\n", field, current[field], camera.shutter.apex, current.apex)
760
            end
761
            if camera.shutter.raw ~= current.raw then
762
                printf("Error: shutter set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.shutter.raw, current.raw)
763
764
765
            end
        end
    end
766
    camera.shutter.raw = old_value
767

768
    request_mode(MODE.M, "M")
769
    old_value = camera.iso.raw
770
    printf("Setting ISO to random values...\n")
771
    for k = 1,100 do
772
773
        local method = math.random(1,3)
        local d = nil
774
        -- 500D: max ISO 3200
775
        if method == 1 then
776
            local iso = math.random(100, 3200)
777
778
779
780
781
            if math.random(1,2) == 1 then
                camera.iso.value = iso
            else
                camera.iso = iso
            end
782
783
            d = math.abs(math.log(camera.iso.value,2) - math.log(iso,2))
        elseif method == 2 then
784
            local apex = math.random(5*100,10*100)/100
785
786
787
            camera.iso.apex = apex
            d = math.abs(camera.iso.apex - apex)
        elseif method == 3 then
788
            local raw = math.random(72, 112)
789
790
791
792
793
794
            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
795
            printf("Error: ISO delta %s EV\n", d)
796
797
798
        end

        -- ISO and Sv (APEX) should be consistent
799
        local expected_apex = math.log(camera.iso.value/3.125, 2)
800
        if math.abs(expected_apex - camera.iso.apex) > 0.2 then
801
            printf("Error: ISO %s != Sv%s, expected %s\n", camera.iso.value, camera.iso.apex, expected_apex)
802
803
804
805
806
        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
807
808
809
810
            local current = {}
            current.apex  = camera.iso.apex
            current.value = camera.iso.value
            current.raw   = camera.iso.raw
811
            
812
            camera.iso[field] = current[field]
813
            
814
            if camera.iso.value ~= current.value then
815
                printf("Error: ISO set to %s=%s, got %s, expected %s\n", field, current[field], camera.iso.value, current.value)
816
            end
817
            if camera.iso.apex ~= current.apex then
818
                printf("Error: ISO set to %s=%s, got Sv%s, expected Sv%s\n", field, current[field], camera.iso.apex, current.apex)
819
            end
820
            if camera.iso.raw ~= current.raw then
821
                printf("Error: ISO set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.iso.raw, current.raw)
822
823
824
            end
        end
    end
825
    camera.iso.raw = old_value
826

a1ex's avatar
a1ex committed
827
    if camera.aperture.min.raw == camera.aperture.max.raw then
828
        printf("This lens does not have variable aperture (skipping test).\n")
a1ex's avatar
a1ex committed
829
830
    else
        request_mode(MODE.M, "M")
831
        old_value = camera.aperture.raw
832
        printf("Setting aperture to random values...\n")
a1ex's avatar
a1ex committed
833
        for k = 1,100 do
834
835
836
            local method = math.random(1,3)
            local d = nil
            local extra_tol = 0
a1ex's avatar
a1ex committed
837
838
            if method == 1 then
                local av = math.random(round(camera.aperture.min.value*10), round(camera.aperture.max.value*10)) / 10
839
840
841
842
843
                if math.random(1,2) == 1 then
                    camera.aperture.value = av
                else
                    camera.aperture = av
                end
a1ex's avatar
a1ex committed
844
                d = math.abs(math.log(camera.aperture.value,2) - math.log(av,2)) * 2
845
846
                -- 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
847
848
849
850
851
852
853
854
855
856
857
            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
858
859
860
            -- 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
861
                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
862
863
864
            end

            -- aperture and Av (APEX) should be consistent
865
            local expected_apex = math.log(camera.aperture.value, 2) * 2
a1ex's avatar
a1ex committed
866
            if math.abs(expected_apex - camera.aperture.apex) > 0.2 then
867
                printf("Error: aperture %s != Av%s, expected %s\n", camera.aperture.value, camera.aperture.apex, expected_apex)
a1ex's avatar
a1ex committed
868
869
870
871
872
            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
873
874
875
876
                local current = {}
                current.apex  = camera.aperture.apex
                current.value = camera.aperture.value
                current.raw   = camera.aperture.raw
a1ex's avatar
a1ex committed
877
                
878
                camera.aperture[field] = current[field]
a1ex's avatar
a1ex committed
879
                
880
                if camera.aperture.value ~= current.value then
881
                    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
882
                end
883
                if camera.aperture.apex ~= current.apex then
884
                    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
885
                end
886
                if camera.aperture.raw ~= current.raw then
887
                    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
888
889
890
                end
            end
        end
891
        camera.aperture.raw = old_value
a1ex's avatar
a1ex committed
892
893
    end

894
    request_mode(MODE.AV, "Av")
895
    old_value = camera.ec.raw
896
    printf("Setting EC to random values...\n")
897
    for k = 1,100 do
898
899
        local method = math.random(1,2)
        local d = nil
900
901
        if method == 1 then
            local ec = math.random(-2*100, 2*100) / 100
902
903
904
905
906
            if math.random(1,2) == 1 then
                camera.ec.value = ec
            else
                camera.ec = ec
            end
907
908
909
910
911
912
913
914
915
            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
916
            printf("Error: EC delta %s EV\n", d)
917
918
919
        end

        -- EC and raw should be consistent
920
        local expected_ec = camera.ec.raw / 8
921
        if math.abs(expected_ec - camera.ec.value) > 0.001 then
922
            printf("Error: EC raw %s != %s EV, expected %s EV\n", camera.ec.raw, camera.ec.value, expected_ec)
923
924
925
926
927
        end

        -- setting EC to the same value, using any method (value,raw)
        -- should not change anything
        for i,field in pairs{"value","raw"} do
928
929
930
            local current = {}
            current.value = camera.ec.value
            current.raw   = camera.ec.raw
931
            
932
            camera.ec[field] = current[field]
933
            
934
            if camera.ec.value ~= current.value then
935
                printf("Error: EC set to %s=%s, got %s, expected %s EV\n", field, current[field], camera.ec.value, current.value)
936
            end
937
            if camera.ec.raw ~= current.raw then
938
                printf("Error: EC set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.ec.raw, current.raw)
939
940
941
            end
        end
    end
942
    camera.ec.raw = old_value
943
944

    -- copy/paste & replace from EC (those two should behave in the same way)
945
    old_value = camera.flash_ec.raw
946
    printf("Setting Flash EC to random values...\n")
947
    for k = 1,100 do
948
949
        local method = math.random(1,2)
        local d = nil
950
951
        if method == 1 then
            local fec = math.random(-2*100, 2*100) / 100
952
953
954
955
956
            if math.random(1,2) == 1 then
                camera.flash_ec.value = fec
            else
                camera.flash_ec = fec
            end
957
958
959
960
961
962
963
964
965
            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
966
            printf("Error: FEC delta %s EV\n", d)
967
968
969
        end

        -- EC and raw should be consistent
970
        local expected_fec = camera.flash_ec.raw / 8
971
        if math.abs(expected_fec - camera.flash_ec.value) > 0.001 then
972
            printf("Error: FEC raw %s != %s EV, expected %s EV\n", camera.flash_ec.raw, camera.flash_ec.value, current.expected_fec)
973
974
975
976
977
        end

        -- setting EC to the same value, using any method (value,raw)
        -- should not change anything
        for i,field in pairs{"value","raw"} do
978
979
980
            local current = {}
            current.value = camera.flash_ec.value
            current.raw   = camera.flash_ec.raw
981
            
982
            camera.flash_ec[field] = current[field]
983
            
984
            if camera.flash_ec.value ~= current.value then
985
                printf("Error: FEC set to %s=%s, got %s, expected %s EV\n", field, current[field], camera.flash_ec.value, current.value)
986
            end
987
            if camera.flash_ec.raw ~= current.raw then
988
                printf("Error: FEC set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.flash_ec.raw, current.raw)
989
990
991
            end
        end
    end
992
    camera.flash_ec.raw = old_value
993

994
995
    printf("Exposure tests completed.\n")
    printf("\n")
996
997
end

998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
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

1010
function test_camera_take_pics()
alex@thinkpad's avatar
alex@thinkpad committed
1011
    printf("Testing picture taking functions...\n")
1012
1013
1014

    request_mode(MODE.M, "M")
    camera.shutter = 1/50
1015
    sleep(2)
1016
1017
1018
    
    printf("Snap simulation test...\n")
    assert(menu.set("Shoot Preferences", "Snap Simulation", 1))
1019
    local initial_file_num = dryos.shooting_card.file_number
1020
1021
1022
1023
    camera.shoot()
    assert(dryos.shooting_card.file_number == initial_file_num)
    assert(menu.set("Shoot Preferences", "Snap Simulation", 0))

1024
    sleep(2)
1025
1026

    printf("Single picture...\n")
1027
    -- let's also check if we can find the image file
1028
    initial_file_num = dryos.shooting_card.file_number
1029
1030
1031
1032
1033
1034
1035
1036
    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)

1037
1038
1039
1040
    -- 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)

1041
    camera.shoot()
1042

1043
1044
1045
1046
1047
1048
1049
    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
1050
    assert(dryos.shooting_card.dcim_dir.path == tostring(dryos.shooting_card.dcim_dir))
1051
1052
1053
1054
    local image_path_dcim =
        dryos.shooting_card.dcim_dir.path ..
        dryos.image_prefix ..
        string.format("%04d", dryos.shooting_card.file_number)
1055
1056
    assert(image_path_dcim == dryos.shooting_card:image_path(0, ""))

1057
    -- but either CR2 or JPG should be there afterwards (or maybe both)
1058
    assert((dryos.shooting_card.file_number - initial_file_num) % 9999 == 1)
1059
1060
1061
1062
1063
    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)

1064
1065
1066
1067
1068
1069
1070
    -- 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)

1071
    sleep(2)
1072
1073
1074
1075

    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
1076
1077
1078
1079
1080

    -- 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_")
1081
1082
1083
1084
1085
1086
    -- 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")
1087
1088
1089
1090
1091
    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)

1092
    camera.burst(2)
1093

1094