Lチカで始めるテスト自動化(7)タイムスタンプの保存

##1. はじめに
Lチカで始めるテスト自動化シリーズ第七弾です。

テストスクリプトの各コマンドを実行するたびに現在時刻を取得し、実行結果とともに保存するようにします。

これまでの記事はこちらをご覧ください。

1. [Lチカで始めるテスト自動化](https://qiita.com/pbjpkas/items/fd4bd2689deda2d793aa)
2. [Lチカで始めるテスト自動化(2)テストスクリプトの保守性向上](https://qiita.com/pbjpkas/items/337bce392281ee5f0526)
3. [Lチカで始めるテスト自動化(3)オシロスコープの組込み](https://qiita.com/pbjpkas/items/b80b7e199a5aed4688a4)
4. [Lチカで始めるテスト自動化(4)テストスクリプトの保守性向上(2)](https://qiita.com/pbjpkas/items/a02c4128baf5a3f2a46c)
5. [Lチカで始めるテスト自動化(5)WebカメラおよびOCRの組込み](https://qiita.com/pbjpkas/items/9af9e6441e1e20c5909e)
6. [Lチカで始めるテスト自動化(6)AI(機械学習)を用いたPass/Fail判定](https://qiita.com/pbjpkas/items/405d55f809da1221b867)

##2. テストランナーの改修
[第六弾の付録A.2](https://qiita.com/pbjpkas/items/405d55f809da1221b867#a2-test-runnerpy%E3%81%AE%E3%82%BD%E3%83%BC%E3%82%B9)に掲載したtest-runner.pyとの差分を以下に示します。

```sh:test-runner.py(第六弾と第七弾のソースの差分)
$ diff test-runner_06.py test-runner_07.py
11a12
> import datetime
133a135
>                 timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
248a251
>                     cmd.insert(0,timestamp)
252a256
>                     cmd.insert(0,timestamp)
```

##3. テスト実行結果
###3.1 テストスクリプト
テストがPassするケースとFailするケースの両方を試すため、意図的にTensorFlowの画像識別のスコアの期待値を0.98にしています。

```sh:script.csv
#
exec_labelimg,str_1234_crop.jpg,str1234
eval_dbl_gt,0.98
exec_labelimg,str_abcd_crop.jpg,strabcd
eval_dbl_gt,0.98
#
```

###3.2 テスト実行結果
Passするケース、Failするケースのどちらも1カラム目にタイムスタンプが追加されました。

```sh:result.csv
2020/06/22 22:50:08,#,OK
2020/06/22 22:50:08,exec_labelimg,str_1234_crop.jpg,str1234,0.99075866,OK
2020/06/22 22:50:22,eval_dbl_gt,0.98,OK
2020/06/22 22:50:22,exec_labelimg,str_abcd_crop.jpg,strabcd,0.94599265,OK
2020/06/22 22:50:34,eval_dbl_gt,0.98,NG
```

##4. おわりに
- TensorFlowの画像識別は時間がかかるなあと思っていたのですが2回実行してそれぞれ14秒、12秒かかっていることがわかりました。

##A. 付録 test-runner.pyのソース

```python:test-runner.py
#!/usr/bin/python3

#
# This software includes the work that is distributed in the Apache License 2.0
#

from time import sleep
import serial
import codecs
import csv
import platform
import datetime
import sys
import subprocess
import visa
import cv2
from PIL import Image
import pyocr
import pyocr.builders

UNINITIALIZED = 0xdeadbeef

def open_cam(camera_number):
    return cv2.VideoCapture(camera_number)

def close_cam(cam):
    if cam != UNINITIALIZED:
        cam.release()

def capture_cam(cam, filename):
    if cam != UNINITIALIZED:
        _, img = cam.read()
        cv2.imwrite(filename, img)
        return True
    else:
        print("CAM Not Ready.")
        return False

def crop_img(filename_in, v, h, filename_out):
    img = cv2.imread(filename_in, cv2.IMREAD_COLOR)
    v0 = int(v.split(':')[0])
    v1 = int(v.split(':')[1])
    h0 = int(h.split(':')[0])
    h1 = int(h.split(':')[1])
    img2 = img[v0:v1, h0:h1]
    cv2.imwrite(filename_out, img2)
    return True

def open_ocr():
    ocr = pyocr.get_available_tools()
    if len(ocr) != 0:
        ocr = ocr[0]
    else:
        ocr = UNINITIALIZED
        print("OCR Not Ready.")
    return ocr

def exec_ocr(ocr, filename):
    try:
        txt = ocr.image_to_string(
            Image.open(filename),
            lang = "eng",
            builder = pyocr.builders.TextBuilder()
        )
    except:
        print("OCR Not Ready.")
    else:
        return txt

def exec_labelimg(filename, label_string):
    if platform.system() == "Windows" :
        python = "python"
        grep = "findstr"
    else:
        python = "python3"
        grep = "grep"
    
    cmd = python + " label_image.py --graph=c:\\tmp\\output_graph.pb --labels=c:\\tmp\\output_labels.txt \
        --input_layer=Placeholder --output_layer=final_result --image=" + filename + \
        "|" + grep + " " + label_string
    print(cmd)
    log = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
    ret = log.stdout.strip().decode("utf-8").split(" ")[1]
    return ret

def serial_write(h, string):
    if h != UNINITIALIZED:
        string = string + '\n'
        string = str.encode(string)
        h.write(string)
        return True
    else:
        print("UART Not Initialized.")
        return False

def close_uart(h):
    if h != UNINITIALIZED:
        h.close()
    else:
        #print("UART Not Initialized.")
        pass

def open_dso():
    rm = visa.ResourceManager()
    resources = rm.list_resources()
    #print(resources)
    for resource in resources:
        #print(resource)
        try:
            dso = rm.open_resource(resource)
        except:
            print(resource, "Not Found.")
        else:
            print(resource, "Detected.")
            return dso

    #Throw an error to caller if none succeed.
    return dso

def main():
    is_passed = True
    val = str(UNINITIALIZED)
    cam = UNINITIALIZED
    ocr = UNINITIALIZED
    dso = UNINITIALIZED
    uart = UNINITIALIZED

    with codecs.open('script.csv', 'r', 'utf-8') as file:
        script = csv.reader(file, delimiter=',', lineterminator='\r\n', quotechar='"')

        with codecs.open('result.csv', 'w', 'utf-8') as file:
            result = csv.writer(file, delimiter=',', lineterminator='\r\n', quotechar='"')

            for cmd in script:
                timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
                print(cmd)

                if "#" in cmd[0]:
                    pass

                elif cmd[0] == "sleep":
                    sleep(float(cmd[1]))

                elif cmd[0] == "open_cam":
                    cam = open_cam(int(cmd[1]))

                elif cmd[0] == "close_cam":
                    close_cam(cam)
                    cam = UNINITIALIZED

                elif cmd[0] == "capture_cam":
                    ret = capture_cam(cam, cmd[1])
                    if ret == False:
                        is_passed = False

                elif cmd[0] == "crop_img":
                    crop_img(cmd[1], cmd[2], cmd[3], cmd[4])

                elif cmd[0] == "open_ocr":
                    ocr = open_ocr()
                    if ocr == UNINITIALIZED:
                        is_passed = False

                elif cmd[0] == "exec_ocr":
                    try:
                        val = exec_ocr(ocr, cmd[1])
                    except:
                        is_passed = False
                    else:
                        cmd.append(str(val))

                elif cmd[0] == "exec_labelimg":
                    try:
                        val = exec_labelimg(cmd[1], cmd[2])
                    except:
                        is_passed = False
                    else:
                        cmd.append(str(val))

                elif cmd[0] == "open_dso":
                    try:
                        dso = open_dso()
                    except:
                        is_passed = False

                elif cmd[0] == "dso":
                    try:
                        if "?" in cmd[1]:
                            val = dso.query(cmd[1]).rstrip().replace(",", "-")
                            cmd.append(val)
                        else:
                            dso.write(cmd[1])
                    except:
                        is_passed = False

                elif cmd[0] == "open_uart":
                    if len(cmd) == 2:
                        dsrdtr_val = 1
                    else:
                        dsrdtr_val = int(cmd[2])
                    try:
                        uart = serial.Serial(cmd[1], 115200, timeout=1.0, dsrdtr=dsrdtr_val)
                    except:
                        is_passed = False

                elif cmd[0] == "send":
                    ret = serial_write(uart, cmd[1])
                    if ret == False:
                        is_passed = False

                elif cmd[0] == "rcvd":
                    try:
                        val = uart.readline().strip().decode('utf-8')
                        cmd.append(val)
                    except:
                        is_passed = False

                elif cmd[0] == "eval_str_eq":
                    if str(val) != str(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_int_eq":
                    if int(val) != int(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_int_gt":
                    if int(val) < int(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_int_lt":
                    if int(val) > int(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_dbl_eq":
                    if float(val) != float(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_dbl_gt":
                    if float(val) < float(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_dbl_lt":
                    if float(val) > float(cmd[1]):
                        is_passed = False

                else:
                    cmd.append("#")

                if is_passed == True:
                    cmd.append("OK")
                    cmd.insert(0,timestamp)
                    print(cmd)
                    result.writerow(cmd)
                else:
                    cmd.append("NG")
                    cmd.insert(0,timestamp)
                    print(cmd)
                    result.writerow(cmd)
                    close_uart(uart)
                    close_cam(cam)
                    print("FAIL")
                    sys.exit(1)

    if is_passed == True:
        close_uart(uart)
        close_cam(cam)
        print("PASS")
        sys.exit(0)

main()
```