kawasin73のブログ

技術記事とかいろんなことをかくブログです

俺はビッグエンディアンでテストがしたいんだ!!!

この記事は、ソフトウェアテスト #2 Advent Calendar 2018 - Qiita の1日目です。

github.com/kawasin73/bitset という Go 言語のビットベクトルのライブラリを作りました。このライブラリはバイトオーダーがビッグエンディアンのマシンでもリトルエンディアンのマシンでも同じ振る舞いをするように実装されています。

ビッグエンディアンとリトルエンディアンのそれぞれのマシンで正しく動くことを確認するテストを Travis CI で実行する手法を試行錯誤したので紹介します。

ビッグエンディアン、リトルエンディアンとは

エンディアン( Endianness )とはバイトの並び順のことで、ビッグエンディアン( Big Endian )とリトルエンディアン( Little Endian )の2種類があります。(ミドルエンディアンなど他のエンディアンもあるらしいですがごく少数派らしいです)また、バイトオーダーとも呼ばれます。

ビッグエンディアンは上位のバイトから並べていく方式で、リトルエンディアンは下位のバイトから並べていく方式です。

例えば Go 言語の uint32 の場合 32 ビットの整数であるためこれは 4 バイトで表されます。

16進数で 0x1234abcd と表される数値は、ビッグエンディアンでは 0x12 | 0x34 | 0xab | 0xcd と、リトルエンディアンでは 0xcd | 0xab | 0x34 | 0x12 と表されます。

エンディアン - Wikipedia

エンディアンは CPU のアーキテクチャによって変わります。よく使われている amd64 のCPU(IntelAMD などの CPU)では、リトルエンディアンです。

また、CPU によってはビッグエンディアンとリトルエンディアンを指定できる CPU もあるみたいです。

同一プロセスのメモリ上の値は全て同じエンディアンで扱われているため、通常のメモリ上の値を操作するプログラミングをする場合はエンディアンを気にする必要はありません。

しかし、マシンをまたいでバイナリデータをやり取りする場合はこのエンディアンに注意しないと、マシンによって全く異なる値に認識されてしまうことになります。(TCP/IP などのネットワークプロトコル操作やバイナリエンコードされたファイルを異なるマシンで扱うときなど)

両方のエンディアンでテストする

今回作った github.com/kawasin73/bitset は、ファイルに永続化されたビットベクトルが異なるマシンで利用されるユースケースを考慮して、どのエンディアンのマシンでも指定されたエンディアンでビットベクトルを扱うようにしています。

そのため、ビッグエンディアンとリトルエンディアンそれぞれのマシンで正しく動いているかをテストする必要がありました。

今回は自動テスト環境として Travis CI を利用することにしましたが、残念ながら Travis CI は amd64アーキテクチャの CPU にのみ対応しており、CPU のアーキテクチャの変更はできませんでした。

そのため、ビッグエンディアンの環境でのテストを何らかの方法でエミュレートして実行する必要があります。

今回は、 qemu-user-static という CPU のエミュレータ を使ってビッグエンディアン環境でのテストを行うことにしました。

qemu から go test を呼び出す

qemu-user-static は様々な CPU アーキテクチャをエミュレートして実行バイナリを実行してくれるソフトウェアです。

multiarch/qemu-user-static リポジトリアーキテクチャごとのエミュレータがアップロードされているため、そこからダウンロードして利用することにしました。(apt-get などでインストールできますが詳しくは後述)

以下のように qemu-<cpu architecture>-static の引数にそのアーキテクチャ用の実行バイナリを渡すことで実行できます。

# ppc64 アーキテクチャの場合
$ qemu-ppc64-static ./some-ppc64-binary

go 言語自体(go buildgo test などを行うツール群)は、Linux OS では amd64, 386, arm, arm64, s390x, ppc64le で提供されていますが、このうちビッグエンディアンなのは s390x のみです。(Getting Started - The Go Programming Language

しかし、qemu-s390x-static go test を実行してもエラーが発生してしまうため go test を直接実行する手法は断念 しました。

$ qemu-s390x-static go test
Illegal instruction (core dumped)

テストの実行をコンパイルする

Go のテストは -c オプションをつけることで、テストを実行するバイナリをコンパイルします。また、-o をつけることでテストバイナリ名を指定できます。

$ GOOS=linux GOARCH=mips64 go test -c -o bitset.test

この手法を使い、コンパイルされたテスト実行バイナリを qemu を使って実行することにしました。

Go 言語では以下の CPU アーキテクチャに対応したバイナリをクロスコンパイルできます。

# go tool dist list | grep linux
linux/386
linux/amd64
linux/arm
linux/arm64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/s390x

これらのうちビッグエンディアンアーキテクチャppc64, mips, mips64, s390x です。(https://github.com/tsuna/endian/blob/master/big.go#L6

今回は、ppc64 アーキテクチャで正しく動くかをテストすることにしました。

以下の .travis.yml は、Go 言語 1.11 1.10 1.9 の3つのバージョンで、それぞれリトルエンディアン(amd64)とビッグエンディアンppc64)で正しい挙動をすることをテストしています。

language: go

os:
  - linux

before_install:
  - wget https://github.com/multiarch/qemu-user-static/releases/download/v3.0.0/qemu-ppc64-static
  - chmod 755 qemu-ppc64-static

go:
  - "1.11"
  - "1.10"
  - "1.9"

script:
  - GOOS=linux GOARCH=ppc64 go test -c -o bitset.test
  - go test ./... -v
  - ./qemu-ppc64-static ./bitset.test -test.v

これによって、リトルエンディアンとビッグエンディアンの両方でテストすることが可能になりました。

qemu-user-static のインストール

今回は、multiarch/qemu-user-static というリポジトリにアップロードされている qemu-ppc64-static を利用しましたが、qemu-user-static 自体は、apt-get でインストールできるので、そちらでインストールすることが望ましいです。

$ sudo apt-get update
$ sudo apt-get install -y qemu-user-static

しかし apt-get でインストールした qemu-user-static ではテストが途中で落ちる謎のバグがあるため、apt-get でのインストールは現在も格闘中です。

github.com

最後に

何とかビッグエンディアンとリトルエンディアンでテストを行うことができました。よかったです。

これの試行錯誤の過程は github.com/kawasin73/bitset リポジトリの issue に記録しています。

github.com

github.com