MacやLinuxでPICTを使う

組合せテストツールのPICTの解説で、表題についてよく聞かれるのでメモ。

PICTについては、専用のバイナリファイルで配布されていたこともあり、今までWindowsで使用されてきた。
ただ去年からGithubオープンソース化され、gccやclangで自由にビルドできるようになった。それに伴い、MacLinuxでもWindowsと同じぐらい手軽に利用できるようになっている。

あえて言うまでもないかもしれないが、導入方法は以下の通り。最近のgccやclangが使え、makeできる環境であれば、OSは問わない。

  1. https://github.com/Microsoft/pict」にて、Download ZIPからファイルダウンロード、解凍
  2. 解答したディレクトリで「make」実行。同ディレクトリにバイナリpictができる。

使用例

このディレクトリに、例えば以下のようなファイルsample.txtを作成する

OS:Win, Mac, Ubuntu
Compiler:GCC, clang
Lang:c, c++, asm

そこで以下のコマンドを実行する。

./pict sample.txt

すると以下のように2ワイズカバレッジ100%の組合せ一覧が出力される。

OS	Compiler	Lang
Win	GCC	asm
Ubuntu	clang	asm
Mac	GCC	c
Mac	clang	c++
Win	clang	c
Ubuntu	GCC	c++
Win	clang	c++
Ubuntu	clang	c
Mac	GCC	asm

clang/gccに組み込まれたAddressSanitizer/LeakSanitizerでメモリエラーを捕捉する

C/C++でのユニットテストによるメモリリーク検出 - 千里霧中の補足。

メモリエラーの検出方法についてだけれど、最近のclangやgccだと、AddressSanitizerという動的解析ツールが組み込まれており、それを活用できる。

使用する場合はコンパイラオプション「-fsanitize=address」「-fsanitize=leak」等を指定する。

題材

例えば以下のコードを対象にする。

//main.c
#include <stdio.h>
#include <stdlib.h>

void hoge(void)
{
        int *a_buff = (int *)malloc(5 * sizeof(int));
        a_buff[10] = 8;
}

int main(void)
{
        printf("test\n");
        hoge();
        return 0;
}


これを普通にコンパイルして実行すると「test」が表示されるだけで、特にエラーなどは検出されない。

AddressSanitizerでの不正なメモリアクセスの検出

一方で、以下のオプションでコンパイルして、AddressSanitizerを有効化する。

gcc -g -fsanitize=address main.c

これで実行すると、「a_buff[10] = 8」の実行タイミングで以下のエラーメッセージが出力されるようになる。

==23801==ERROR: AddressSanitizer: heap-buffer-overflow on address 
略
==23801==atos returned: An admin user name and password is required to enter Developer Mode.
    #0 0x113d63d4d in hoge (*****/./a.out+0x110000d4d)
    #1 0x113d63e04 in main (*****/./a.out+0x110000e04)
    #2 0x1ff199451ac in start (/usr/lib/system/libdyld.dylib+0x35a0)
    #3 0x0  (<unknown module>)
略
SUMMARY: AddressSanitizer: heap-buffer-overflow ??:0 hoge
Shadow bytes around the buggy address:
以下略

LeakSanitizerでのメモリリークの検出

次に以下のオプションでコンパイルして、LeakSanitizerを有効化する。

gcc -g -fsanitize=leak main.c

これを実行すると、アプリケーション終了時点で以下のエラーメッセージが出力されるようになる。

==6316==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 20 byte(s) in 1 object(s) allocated from:
略

TPI NEXTファンデーション試験を受けた

TPI NEXTについて、日本語化を期に実践し始めているのと、現在TPI NEXT入門ワークショップを作っている背景から、最近、知識確認としてその資格試験であるファンデーション試験を受験した。
この試験、日本語で情報がほとんどなかったので今回参考までにまとめたい。

難易度

問題は以下で公開されているシラバスとサンプルに沿った内容だった。
EXIN - Your ICT Competence Partner - 資格プログラム

試験の内容は選択式の知識確認問題で、かなり易しいと感じた。指標としては例えばJSTQBのFLよりも更に易しいと思う。今回、日々の実践や講演資料作りでTPI NEXT本を読んでいた以外に、特に対策せず受験したが、解けない問題はなかった。前述のシラバス通りに対応すれば合格できると思う。

注意点

ただ注意点として、日本語表現にかなり問題があった。

問題の文章は、英語を直訳してそのまま載せたレベルで、「てにをは」のような日本語文法が破綻したものが複数あったと思う。元の英文を想像しないと理解が難しい問題があった。

またTPI Nextの日本語書籍と翻訳が統一されていない。用語の不統一で気になったもののうち、覚えているものを幾つかピックアップする。

  • 「キーエリア達成のコツ」は試験問題では「イネーブラ」と記述
  • 「目指すべき状況」は試験問題では「対象の状態」と記述
  • 「テスト業務の専門性」は試験問題では「テストの専門職」と記述
  • 「メトリクス」は試験問題では「測定基準」と記述

用語も、基本的に直訳調だった。元の英語の単語をイメージしないと混乱しがちだと感じるものがあった。

あと、書籍の記述の暗記を要求する、丸暗記系の問題が少しあったのもの気になった。問題の公開はできないようなので内容を簡略化して書くが、例えば「『対象の状態をマトリクスで表現する方法はどれか?』『選択肢:セルの背景色を変える、識別用の文字を書く、・・・』」といった問題があった(個人的に表現方法は何でも良いと思っているし、TPI NEXTでも強制は行っていないように見える。書籍でもフォントの色で区別しているものがある。そのため本質的にはどれでも良いような気がする。ただ原著では背景色で区別した図を載せているので、「背景色を変える」を正解としているのだと思う)。

さいごに

この資格は、TPI NEXTの基礎知識のチェックとしては有望かもしれない。
しかし日本語表現に問題が多い印象を持った。日本語書籍の方は、技術的なニュアンスも含めてかなり洗練された翻訳を行っているので、そのうち書籍から本試験に翻訳是正のフィードバックがかかりそうな気がする。そのため受験は様子を見て行ったほうがよいかもしれない。

「開発スピードと品質の両立実践法」登壇

先日、アイティメディア主催の「開発スピードと品質の両立実践法」というイベントにて、インタビュー形式の基調講演と、パネルディスカッションに登壇させていただきました。
https://itmedia.smartseminar.jp/public/seminar/view/723

イベントのテーマは「品質とスピードを両立するためには」「リリースでトラブルを起こさないためには」といった普遍的なもので、フロントローディングの話、反復開発での計画作りとベロシティ最適化の話、エンジニア個々人が地力を高めていきましょうといった話をさせていただきました。
(企業セミナーではツール、プロセス、具体的なプラクティスと、属人性の少ない手法や技術が多い印象ですので、他の登壇者との差別化として、あえて当たり前というか、忘れてはならない現場の基礎的な話にフォーカスさせていただきました)

なお今回はディスカッションなどでツールベンダやツール導入コンサルのお話も直接聞け、大変有意義な機会となりました。
声をかけていただいたアイティメディア様、またその他開催関係者や参加者の方々に深くお礼申し上げます。

VimでVerilogのコーディング環境を確保

VimにおけるVerilog開発環境については、ここ数年で色々なプラグインが標準サポートしてきた。
整理のために、今回はVerilog関連のプラグインの設定を少しまとめたいと思う。

スニペット

今年になって、neosnippetsがVerilogを標準サポートしている。
neosnippet-snippets/verilog.snip at master · Shougo/neosnippet-snippets · GitHub
そのためNeoBundle等でneosnippetsを入れると、そのままVerilog用のスニペットを利用できるようになる。

構文チェック

Verilogの構文チェックについては、syntasticが少し前から標準サポートしている。
NeoBundleでsyntasticを入れる場合は以下を.vimrcへ。

NeoBundle 'scrooloose/syntastic'

またこのVerilogの構文チェックを有効化するにはVerilatorのインストールが必要になる。
homebrew経由だと以下でインストール。

brew install verilator

なおこのsyntastic & verilatorのサポートは結構強力で、入れた時は最強のVerilogエディター誕生みたいな気分になれる。
以下はファイル保存時に表示される構文チェックの結果例。異常な構文の箇所に「>>」赤文字が、最下部に何が異常かの警告が出る。

f:id:goyoki:20150625220954p:plain

f:id:goyoki:20150625221012p:plain

ブロック間のジャンプ

matchit.vimを有効にすると、そのままbegin〜end、module〜endmoduleなどVerilogのブロック間を%でジャンプできるようになる。またhl_matchit.vimなども普通に動く。
matchit.vimはデフォルトで格納されているので、有効化は.vimrcに以下を挿入すればできる。

:source $VIMRUNTIME/macros/matchit.vim

入力補完

verilog.dict vimrc」などでウェブ検索すると、Verilog固有の辞書ファイルが見つかる。参考にして自作したり、ライセンスの範囲内で流用したりして、neocomplcache・neocompleteなどに登録すれば、Verilogの入力補完ができるようになる。

PyAutoGUIでGUIの自動操作

 PyAutoGUIがシンプルでかなり使いやすかったのでメモ。
 PyAutoGUIは、GUIの自動操作をサポートするPython用ライブラリ。マウス操作、キーボード操作、スクリーンキャプチャ、指定した画像のマッチングと座標取得などの機能を提供する。シンプルだけれど、自動操作に必要な機能がひと通り揃っている。

 使用例として、GUI縛りのツールを自動操作させ、結果が得られたらその画面キャプチャを保存するスクリプトを載せる。

#-*- coding: utf-8 -*-
#run_and_capture.py
import pyautogui, time
from datetime import datetime

# 処理開始ボタンを探してクリックする
position = pyautogui.locateCenterOnScreen('startbutton.png') #startbutton.pngは処理開始ボタンのキャプチャ画像
pyautogui.click(position, duration = 0.2)

nsec = 0
while True:
	posPassInfo = pyautogui.locateOnScreen('pass.png') #pass.pngは成功時表示のキャプチャ画像
	posFailInfo = pyautogui.locateOnScreen('fail.png') #fail.pngは失敗時表示のキャプチャ画像
	if posPassInfo is not None or posFailInfo is not None:
		# 成功あるいは失敗表示を確認できたら、日付名でスクリーンショットを保存
		filename = datetime.now().strftime("%Y%m%d_%H%M%S") + ".png"
		s = pyautogui.screenshot()
		s.save(filename)
		break
	time.sleep(1)
	nsec += 1
	if nsec > 5:
		print("timeout") #処理操作開始後、5s以上経過するまで結果表示を得られなかったらタイムアウトで終了
		break

MPLABシミュレータでのSCLによるソフトウェアの自動操作

MPLABでシミュレータを自動操作する手段にSCL(Stimulus Control Language)のスクリプトがある。
このスクリプトIDEで設定して自動生成することが多いけれど、手書きで作成することも可能だ。

スクリプトの記述力は十分とは言えないものの、ファイル操作・文字列検索のAPIや、標準出力(SimulatorのOutput)を操作するreport()のAPIなど、最低限の機能が揃っている。UARTやテスト用IFがなくとも、シミュレータ上のソフトウェアを自動操作し出力を比較検証することが可能となっており、自動テストにも使えるので、簡単に例をメモ。

記述例

例えばPICで動作するソフトウェアを対象として、UARTでデジタル出力を操作し、その結果を評価するスクリプトならば、以下のようになる。

testbench for "pic12f1822" is
    begin
        process is
            begin
                report("start");
                RCREG <= 16#E2#;
                wait for 10 ms; 
                if RA0 == '1' then
                    report("ra0-0 fail");
                else
                    report("pass");
                end if;
                if RA1 == '0' then
                    report("ra1-1 fail");
                else
                    report("pass");
                end if;
                wait;
        end process;
    end testbench;

また複雑なスクリプトは、MPLABで生成して、手描きで追加編集すると作りやすい。
例えばファイルでリストアップした文字列をUART入力する処理をSCL化すると、以下の様なスクリプトが生成される。
これに「elsif match(〜」の行を追加してオリジナルのイベント命令を追加したり、report()を挿入したりすれば、それなりの自動テストのスクリプトに組み替えられる。

〜
        file_open(status_RCREG, data_file_RCREG, "input_data.txt", read_mode);
        if status_RCREG == open_ok then
            while endfile(data_file_RCREG) == false loop
                readline(data_file_RCREG, pkt_line_RCREG);
                // skip empty line and comment line
                if match(pkt_line_RCREG, "") == true then // do nothing
                elsif match(pkt_line_RCREG, "//") == true then // do nothing
                else
                    if match(pkt_line_RCREG, "wait ") == true then
                        read(pkt_line_RCREG, dummy_RCREG); // to consume 'wait' command
                        read(pkt_line_RCREG, waitime_RCREG);
                        wait for waitime_RCREG;
                        new_packet_RCREG := true;
                    elsif match(pkt_line_RCREG, "rand ") == true then
                        read(pkt_line_RCREG, dummy_RCREG); // to consume 'rand' command
                        read(pkt_line_RCREG, rand_lower_RCREG);
                        read(pkt_line_RCREG, rand_upper_RCREG);
                        read(pkt_line_RCREG, rand_unit_RCREG);
                        random_time(rand_lower_RCREG, rand_upper_RCREG, rand_unit_RCREG,
                                    rand_seed1_RCREG, rand_seed2_RCREG, randime_RCREG);
                        wait for randime_RCREG;
                        new_packet_RCREG := true;
                    else
                        if new_packet_RCREG == true then
                            packetin(pkt_line_RCREG, RCREG, false); // new packet
                            wait until RCREG_packet_done;
                            new_packet_RCREG := false;
                        else
                            packetin(pkt_line_RCREG, RCREG, true); // append to previous
                            wait until RCREG_packet_done;
                        end if;
                    end if;
                end if;
            end loop;
        else
            file_close(data_file_RCREG);
            wait;
        end if;
        file_close(data_file_RCREG);
〜