Read about our upcoming Code of Conduct on this issue

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

5
require("logger")
6

7
8
9
-- global logger
test_log = nil

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

14
15
function request_mode(mode, mode_str)
    while camera.mode ~= mode do
16
        printf("Please switch to %s mode.\n", mode_str, mode)
17
18
19
20
21
22
23
        while camera.mode ~= mode do
            console.show()
            msleep(1000)
        end
    end
end

a1ex's avatar
a1ex committed
24
25
26
27
28
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

29
30
31
32
33
34
35
36
37
38
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

39
40
41
42
-- used for testing Lua 'strict' mode
declared_var = nil

function strict_tests()
43
    printf("Strict mode tests...\n")
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

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

59
60
    printf("Strict mode tests passed.\n")
    printf("\n")
61
62
63
end

function generic_tests()
64
    printf("Generic tests...\n")
65
    print_table("arg")
66
67
68
69
70
71
72
73
74
75
76
77
78
79
    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")
80
81
    printf("Generic tests completed.\n")
    printf("\n")
82
83
end

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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)
106
    printf("Copy test: %s -> %s\n", src, dst)
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
    
    -- 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
131
    assert(io.open(dst, "rb") == nil)
132
133
134
135

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

136
    printf("Copy test OK\n")
137
138
139
end

function append_test(file)
140
    printf("Append test: %s\n", file)
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156

    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
157
    assert(check == data1)
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173

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

176
    printf("Append test OK\n")
177
178
end

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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

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

    printf("File I/O tests completed.\n")
    printf("\n")
227
228
end

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
function test_menu()
    printf("Testing menu API...\n")

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

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

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

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

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

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

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

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

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

    -- turning off should be straightforward
    assert(menu.set("Shoot", "Advanced Bracket", "OFF"))
    assert(menu.get("Shoot", "Advanced Bracket") == 0)
284
    assert(menu.get("Shoot", "Advanced Bracket", "") == "OFF")
285
286
287
288
    msleep(1000)

    assert(menu.set("Shoot", "Intervalometer", 0))
    assert(menu.get("Shoot", "Intervalometer") == 0)
289
    assert(menu.get("Shoot", "Intervalometer", "") == "OFF")
290
291
292
293
294
295
296
    msleep(1000)

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

    -- enter the submenu
297
    assert(menu.select("Intervalometer", "Take a pic every"))
298
299
300
301
302
303
304
305
306
    msleep(1000)

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

    -- actual string will be 10s
    assert(menu.set("Intervalometer", "Take a pic every", "10"))
    assert(menu.get("Intervalometer", "Take a pic every") == 10)
313
    assert(menu.get("Intervalometer", "Take a pic every", "") == "10s")
314
315
316
317
318
    msleep(1000)

    -- integer should work as well - e.g. 1m10s should work now
    assert(menu.set("Intervalometer", "Take a pic every", 70))
    assert(menu.get("Intervalometer", "Take a pic every") == 70)
319
    assert(menu.get("Intervalometer", "Take a pic every", "") == "1m10s")
320
321
322
    msleep(1000)

    -- out of range, should fail
323
    assert(menu.set("Intervalometer", "Take a pic every", 7000000) == false)
324
    assert(menu.get("Intervalometer", "Take a pic every") == 70)
325
    assert(menu.get("Intervalometer", "Take a pic every", "") == "1m10s")
326
327
328
    msleep(1000)

    -- exit submenu
329
    assert(menu.select("Shoot", "Intervalometer"))
330
331
    msleep(1000)

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

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

371
    -- non-existent menus; should fail
372
373
374
375
376
377
378
379
    assert(menu.select("Dinosaur") == false)
    assert(menu.select("Shoot", "Crocodile") == false)

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

    menu.close()

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

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

393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
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

411
412
413
414
415
416
417
418
419
420
421
422
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

423
424
425
function test_multitasking()
    printf("Testing multitasking...\n")

426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
    -- 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)
450
451
452
453
454

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

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

479
function test_camera_exposure()
alex@thinkpad's avatar
alex@thinkpad committed
480
    printf("Testing exposure settings...\n")
481
482
483
484
485
486
    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",
487
488
489
490
491
        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
    )
492
493
494
    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)
495

496
    request_mode(MODE.M, "M")
497
    local old_value = camera.shutter.raw
498
    printf("Setting shutter to random values...\n")
499
    for k = 1,100 do
500
501
        local method = math.random(1,4)
        local d = nil
502
503
        if method == 1 then
            local s = math.random(1,30)
504
505
506
507
508
            if math.random(1,2) == 1 then
                camera.shutter.value = s
            else
                camera.shutter = s
            end
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
            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
528
            printf("Error: shutter delta %s EV\n", d)
529
530
531
532
        end

        -- seconds and ms fields should be consistent
        if math.abs(camera.shutter.value - camera.shutter.ms/1000) > 1e-3 then
533
            printf("Error: shutter %ss != %sms\n", camera.shutter.value, camera.shutter.ms)
534
535
536
        end
        
        -- seconds and apex should be consistent
537
        -- see http://dougkerr.net/Pumpkin/articles/APEX.pdf
538
        local expected_apex = math.log(1/camera.shutter.value,2)
539
        if math.abs(expected_apex - camera.shutter.apex) > 0.01 then
540
            printf("Error: shutter %ss != Tv%s, expected %s\n", camera.shutter.value, camera.shutter.apex, expected_apex)
541
542
543
544
545
        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
546
547
548
549
550
            local current = {}
            current.apex  = camera.shutter.apex
            current.value = camera.shutter.value
            current.ms    = camera.shutter.ms
            current.raw   = camera.shutter.raw
551
552
553
            
            -- 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
554
            if field == "ms" and current.ms < 10 and method ~= 2 then
555
556
557
558
                -- how do you write "continue" in Lua?!
                field = "value"
            end
            
559
            camera.shutter[field] = current[field]
560
            
561
            if camera.shutter.value ~= current.value then
562
                printf("Error: shutter set to %s=%s, got %ss, expected %ss\n", field, current[field], camera.shutter.value, current.value)
563
            end
564
            if camera.shutter.ms ~= current.ms then
565
                printf("Error: shutter set to %s=%s, got %sms, expected %sms\n", field, current[field], camera.shutter.ms, current.ms)
566
            end
567
            if camera.shutter.apex ~= current.apex then
568
                printf("Error: shutter set to %s=%s, got Tv%s, expected Tv%s\n", field, current[field], camera.shutter.apex, current.apex)
569
            end
570
            if camera.shutter.raw ~= current.raw then
571
                printf("Error: shutter set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.shutter.raw, current.raw)
572
573
574
            end
        end
    end
575
    camera.shutter.raw = old_value
576

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

        -- difference between requested and actual ISO should be max 1/3 EV
        if d > 1/3 then
603
            printf("Error: ISO delta %s EV\n", d)
604
605
606
        end

        -- ISO and Sv (APEX) should be consistent
607
        local expected_apex = math.log(camera.iso.value/3.125, 2)
608
        if math.abs(expected_apex - camera.iso.apex) > 0.2 then
609
            printf("Error: ISO %s != Sv%s, expected %s\n", camera.iso.value, camera.iso.apex, expected_apex)
610
611
612
613
614
        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
615
616
617
618
            local current = {}
            current.apex  = camera.iso.apex
            current.value = camera.iso.value
            current.raw   = camera.iso.raw
619
            
620
            camera.iso[field] = current[field]
621
            
622
            if camera.iso.value ~= current.value then
623
                printf("Error: ISO set to %s=%s, got %s, expected %s\n", field, current[field], camera.iso.value, current.value)
624
            end
625
            if camera.iso.apex ~= current.apex then
626
                printf("Error: ISO set to %s=%s, got Sv%s, expected Sv%s\n", field, current[field], camera.iso.apex, current.apex)
627
            end
628
            if camera.iso.raw ~= current.raw then
629
                printf("Error: ISO set to %s=%s, got %s, expected %s (raw)\n", field, current[field], camera.iso.raw, current.raw)
630
631
632
            end
        end
    end
633
    camera.iso.raw = old_value
634

a1ex's avatar
a1ex committed
635
    if camera.aperture.min.raw == camera.aperture.max.raw then
636
        printf("This lens does not have variable aperture (skipping test).\n")
a1ex's avatar
a1ex committed
637
638
    else
        request_mode(MODE.M, "M")
639
        old_value = camera.aperture.raw
640
        printf("Setting aperture to random values...\n")
a1ex's avatar
a1ex committed
641
        for k = 1,100 do
642
643
644
            local method = math.random(1,3)
            local d = nil
            local extra_tol = 0
a1ex's avatar
a1ex committed
645
646
            if method == 1 then
                local av = math.random(round(camera.aperture.min.value*10), round(camera.aperture.max.value*10)) / 10
647
648
649
650
651
                if math.random(1,2) == 1 then
                    camera.aperture.value = av
                else
                    camera.aperture = av
                end
a1ex's avatar
a1ex committed
652
                d = math.abs(math.log(camera.aperture.value,2) - math.log(av,2)) * 2
653
654
                -- 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
655
656
657
658
659
660
661
662
663
664
665
            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
666
667
668
            -- 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
669
                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
670
671
672
            end

            -- aperture and Av (APEX) should be consistent
673
            local expected_apex = math.log(camera.aperture.value, 2) * 2
a1ex's avatar
a1ex committed
674
            if math.abs(expected_apex - camera.aperture.apex) > 0.2 then
675
                printf("Error: aperture %s != Av%s, expected %s\n", camera.aperture.value, camera.aperture.apex, expected_apex)
a1ex's avatar
a1ex committed
676
677
678
679
680
            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
681
682
683
684
                local current = {}
                current.apex  = camera.aperture.apex
                current.value = camera.aperture.value
                current.raw   = camera.aperture.raw
a1ex's avatar
a1ex committed
685
                
686
                camera.aperture[field] = current[field]
a1ex's avatar
a1ex committed
687
                
688
                if camera.aperture.value ~= current.value then
689
                    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
690
                end
691
                if camera.aperture.apex ~= current.apex then
692
                    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
693
                end
694
                if camera.aperture.raw ~= current.raw then
695
                    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
696
697
698
                end
            end
        end
699
        camera.aperture.raw = old_value
a1ex's avatar
a1ex committed
700
701
    end

702
    request_mode(MODE.AV, "Av")
703
    old_value = camera.ec.raw
704
    printf("Setting EC to random values...\n")
705
    for k = 1,100 do
706
707
        local method = math.random(1,2)
        local d = nil
708
709
        if method == 1 then
            local ec = math.random(-2*100, 2*100) / 100
710
711
712
713
714
            if math.random(1,2) == 1 then
                camera.ec.value = ec
            else
                camera.ec = ec
            end
715
716
717
718
719
720
721
722
723
            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
724
            printf("Error: EC delta %s EV\n", d)
725
726
727
        end

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

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

    -- copy/paste & replace from EC (those two should behave in the same way)
753
    old_value = camera.flash_ec.raw
754
    printf("Setting Flash EC to random values...\n")
755
    for k = 1,100 do
756
757
        local method = math.random(1,2)
        local d = nil
758
759
        if method == 1 then
            local fec = math.random(-2*100, 2*100) / 100
760
761
762
763
764
            if math.random(1,2) == 1 then
                camera.flash_ec.value = fec
            else
                camera.flash_ec = fec
            end
765
766
767
768
769
770
771
772
773
            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
774
            printf("Error: FEC delta %s EV\n", d)
775
776
777
        end

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

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

802
803
    printf("Exposure tests completed.\n")
    printf("\n")
804
805
end

806
807
808
809
810
811
812
813
814
815
816
817
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

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

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

    msleep(2000)

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

846
    camera.shoot()
847
848

    -- but either CR2 or JPG should be there afterwards (or maybe both)
849
    assert((dryos.shooting_card.file_number - initial_file_num) % 10000 == 1)
850
851
852
853
854
    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)

855
856
857
858
859
    msleep(2000)

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

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

876
    camera.burst(2)
877

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

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

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

    msleep(2000)

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

930
    camera.bulb(10)
931

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

    printf("Picture taking tests completed.\n")
alex@thinkpad's avatar
alex@thinkpad committed
945
    printf("\n")
946
    msleep(5000)
947
948
end

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

958
    printf("Starting LiveView...\n")
959
960
961
962
963
964
    lv.start()
    assert(lv.enabled, "LiveView did not start")

    msleep(2000)
    
    for i,z in pairs{1, 5, 10, 5, 1, 10, 1} do
965
        printf("Setting zoom to x%d...\n", z)
966
967
968
969
970
        lv.zoom = z
        assert(lv.zoom == z, "Could not set zoom in LiveView ")
        lv.wait(5)
    end

971
    printf("Pausing LiveView...\n")
972
973
974
975
976
977
    lv.pause()
    assert(lv.enabled, "LiveView stopped")
    assert(lv.paused, "LiveView could not be paused")

    msleep(2000)

978
    printf("Resuming LiveView...\n")
979
980
981
982
983
984
    lv.resume()
    assert(lv.enabled, "LiveView stopped")
    assert(not lv.paused, "LiveView could not be resumed")

    msleep(2000)

985
    printf("Stopping LiveView...\n")
986
987
988
989
990
991
992
993
    lv.stop()

    assert(not lv.enabled, "LiveView did not stop")
    assert(not lv.paused,  "LiveView is disabled, can't be paused")
    assert(not lv.running, "LiveView is disabled, can't be running")

    msleep(1000)

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

998
function test_lens_focus()
alex@thinkpad's avatar
alex@thinkpad committed
999
1000
1001
    printf("\n")
    printf("Testing lens focus functionality...\n")
    
1002
    if lens.name == "" then
1003
        printf("This test requires an electronic lens.\n")
1004
1005
1006
1007
1008
        assert(not lens.af, "manual lenses can't autofocus")
        return
    end
    
    if not lens.af then
1009
1010
        printf("Please enable autofocus.\n")
        printf("(or, remove the lens from the camera to skip this test)\n")
1011
1012
1013
1014
        while not lens.af and lens.name ~= "" do
            console.show()
            msleep(1000)
        end
1015
        msleep(1000)
1016
    end
1017

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

1021
    if not lv.running then
1022
1023
1024
1025
        if lens.af then
            printf("Autofocus outside LiveView...\n")
            assert(lens.autofocus())
        end
1026

1027
1028
1029
        lv.start()
        assert(lv.running)
    end
1030

1031
    if lens.af then
1032
        printf("Focus distance: %s\n",  lens.focus_distance)
1033

1034
1035
1036
1037
1038
        printf("Autofocus in LiveView...\n")
        assert(lens.autofocus())

        printf("Focus distance: %s\n",  lens.focus_distance)

1039
1040
        -- note: focus direction is not consistent
        -- some lenses will focus to infinity, others to macro
1041
        printf("Focusing backward...\n")
1042
1043
        while lens.focus(-1,3,true) do end

1044
        -- note: focus distance and position may not be reported right away
1045
        msleep(500)
1046
1047
1048

        printf("Focus distance: %s\n",  lens.focus_distance)
        printf("Focus motor position: %d\n", lens.focus_pos)
1049
        
1050
        for i,step in pairs{3,2,1} do
1051
            for j,wait in pairs{true,false} do
1052
                printf("Focusing forward with step size %d, wait=%s...\n", step, wait)
1053
                local steps_front = 0
1054
                local focus_pos_0 = lens.focus_pos
1055
                while lens.focus(1,step,wait) do
1056
                    printf(".")
1057
1058
                    steps_front = steps_front + 1
                end
1059
1060
1061
                
                msleep(500)

1062
1063
                printf("\n")
                printf("Focus distance: %s\n",  lens.focus_distance)
1064
1065
1066
                printf("Focus motor position: %d\n", lens.focus_pos)
                local focus_pos_1 = lens.focus_pos

1067
1068
                msleep(500)
                
1069
                printf("Focusing backward with step size %d, wait=%s...\n", step, wait)
1070
                local steps_back = 0
1071
                while lens.focus(-1,step,wait) do
1072
                    printf(".")
1073
1074
                    steps_back = steps_back + 1
                end
1075
1076
1077
                
                msleep(500)

1078
1079
                printf("\n")
                printf("Focus distance: %s\n",  lens.focus_distance)
1080
1081
                printf("Focus motor position: %d\n", lens.focus_pos)
                local focus_pos_2 = lens.focus_pos
1082

1083
                msleep(500)
1084

1085
1086
1087
                local motor_steps_front = math.abs(focus_pos_1 - focus_pos_0)
                local motor_steps_back  = math.abs(focus_pos_2 - focus_pos_1)
                local motor_steps_lost  = math.abs(focus_pos_2 - focus_pos_0)
1088
                printf("Focus range: %s steps forward, %s steps backward. \n",  steps_front, steps_back)
1089
1090
1091
                printf("Motor steps: %s forward, %s backward, %s lost. \n",  motor_steps_front, motor_steps_back, motor_steps_lost)

                msleep(500)
1092
            end
1093
        end
1094
        printf("\nFocus test completed.\n")
1095
    else
1096
        printf("Focus test skipped.\n")
1097
    end
1098
    printf("\n")
1099
1100
end

1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
function test_movie()
    printf("\n")
    printf("Testing movie recording...\n")

    -- we must be in photo mode (from previous tests)
    assert(camera.mode ~= MODE.MOVIE)

    -- this should raise error
    local s,e = pcall(movie.start)
    assert(s == false)
    assert(e:find("movie mode"))

    request_mode(MODE.MOVIE, "Movie")
1114
1115
    lv.start()
    assert(lv.running)
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146

    -- this should also raise error, as we are not exactly in LiveView
    lv.pause()
    local s,e = pcall(movie.start)
    assert(s == false)
    assert(e:find("LiveView"))
    menu.close()
    lv.resume()

    -- this should raise error, because the LiveView button has a special
    -- function in ML menu (so we can't just press it to start recording)
    menu.open()
    local s,e = pcall(movie.start)
    assert(s == false)
    assert(e:find("menu"))
    menu.close()

    -- now it should work
    -- hide the console for a nicer look
    console.hide()
    movie.start()
    assert(movie.recording)
    msleep(1000)
    movie.stop()
    assert(not movie.recording)
    console.show()

    printf("Movie recording tests completed.\n")
    printf("\n")
end

1147
1148
1149
1150
1151
1152
function api_tests()
    menu.close()
    console.clear()
    console.show()
    test_log = logger("LUATEST.LOG")

alex@thinkpad's avatar
alex@thinkpad committed
1153
    -- note: each test routine must print a blank line at the end
1154
1155
1156
    strict_tests()
    generic_tests()
    
1157
    printf("Module tests...\n")
1158
    test_io()
1159
    test_menu()
1160
    test_camera_take_pics()
1161
1162
    msleep(1000)
    test_multitasking()
1163
    test_keys()
1164
    test_lv()
1165
    test_lens_focus()
alex@thinkpad's avatar
alex@thinkpad committed
1166
    test_camera_exposure()
1167
    test_movie()
1168
    
1169
    printf("Done!\n")