ジョギング
- 山王公園往復
- 懸垂 5 回イケた
日課
- おやすみ
JAWS-UG 福岡もくもく会
- みんなで集まってワイワイ技術的な話が出来るのは楽しい
- SAP の勉強, かなり難しい...
夕飯は
とんとんで湯豆腐を食べた.
同じマンション住む幼い兄弟が自宅鍵を無くしたというので一緒に探してあげた. 自宅の鍵が無くなるという致命的な状況にも関わらず, 「俺と弟, そしておっちゃん (間違いなく自分のことだと思う) がいればきっと見つかるよね」って, 常にポジティブだったのが印象的だったし, なんだか大人の自分が勇気づけられた気がした. いろいろおもしろいシチュエーションもあったけど, これはまたの機会に.
お昼すぎから外出してカフェで作業. どうしても横の人たちの声が耳に入ってきて煩いなあと思っていたら, その内容がマルチなアレなアレの話っぽくて世の中怖いなあと思った次第. また, お金はコツコツ貯めて行きたいと思う.
自宅近くの居酒屋でとろろ納豆を食べている。
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月9日
輪読会の後, 独りで「とんとん」に. とろろ納豆と地鶏のたたきを食べる.
日曜日に酔った勢いも後押しして「エッサッサ」をやってから体中が痛い. 鍛え方が足りない証拠だ.
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月9日
どうしたんだろう. 体中がずっと痛い.
なんどか佐世保まできているが, 初めて九十九島観光をしてみた. 遊覧船に乗ったり, あごだしラーメンからの佐世保バーガー食べたりと最高だった.
C による C の為のテストフレームワーク https://t.co/DYWKpuwbyY
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月8日
という言い方で良いのかわからないけど, Hello World してみたら使いやすかった.
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月8日
週末はスキマ時間で C 言語の初歩的な勉強を続けた. せっかくなので課題のコードについてテストを書いてみようと思ってしらべたら Unity というフレームワークが良さそうだったので Hello World 的なことをやってみた.
放送大学教養学部の「アルゴリズムとプログラミング」という授業で使われる「アルゴリズムとプログラミング」という教材書籍を自分なりにまとめたものです.
第 2 章では, C 言語の条件分岐に関連する if 文や switch 文等の基本的な内容について触れられています. 課題については, リファクタリング的な内容だったのでテストを書いてリファクタリングしてみました.
尚, 本まとめについては, 以下の Github リポジトリで管理しており, 加筆修正はリポジトリのみ行います.
以下の例だと, 条件式が真 (true
) である場合, 処理が実行されるが, 偽 (false) である場合には実行されない.
if ( 条件式 ) 条件式が真 true だった場合の処理;
以下, C 言語の比較演算子の例.
比較演算子 | 比較演算子の意味 | 比較演算子の意味 (英語) |
---|---|---|
== |
等しい | equal to |
!= |
等しくない | not equal to |
> |
大きい | greater than |
< |
小さい | less than |
>= |
大きいか等しい | greater than or equal to |
<= |
小さいか等しい | less than or equal to |
複数の命令を実行したい場合には, 以下のように波括弧 (カーリーブランケット: curly brankey) の記号 { }
を利用したブロック文を用いる.
if ( 条件式 ) { 処理 1; 処理 2; ... 処理 n; }
以下の例では, 条件式が真となる場合には, 処理 T が実行され, 偽の場合には処理 F が実行される.
if ( 条件式 ) 処理 T; else 処理 F;
複数の命令を実行したい場合には, 以下のようにカーリーブランケットを利用したブロック文を利用する.
if ( 条件式 ) { 処理 T0; 処理 T1; } else { 処理 F0; 処理 F1; }
default
はどの case
にも一致しなかった場合に実行される, default
は省略可能, case
と default
の順序は任意breke
はそれ以降の処理を行わずに switch 文を抜けるswitch ( 式 ) { case 定数式 0: 処理; ... break; case 定数式 1: 処理; ... break; case 定数式 2: 処理; ... break; default: 処理; ... break; }
条件式 ? 式 T : 式 F
以下, if-else 文との比較.
// 条件演算子バージョン result = a > b ? x : y // if-else バージョン if (a > b) { result = x; } else { result = y; }
以下, for 文と goto 文の比較.
# for 文 for ( i = 0; i < 1024; i++ ) { 処理; } # goto 文 i = 0; LABEL_A: if ( i < 1024 ) goto LABEL_B; 処理; i ++; goto LABEL_A LABEL_B:
た, 確かに goto 文は可読性悪い...
以下のコードを変更し, scanf 関数を用いて変数 x と変数 y の値を, キーボード等の標準入力から入力出来るようにしなさい.
/* code: ex2-2.c */ #include <stdio.h> int main() { int x, y; x = 500; y = 700; printf ("X = %d\n", x); printf ("Y = %d\n", y); if (x > y) printf ("X is greater than Y.\n"); else printf ("X is less than or equal to Y.\n"); return 0; }
このコードをコンパイルして実行してみる.
$ gcc ex2-2.c -o ex2-2 $ ./ex2-2 X = 500 Y = 700 X is less than or equal to Y.
これを以下のように改修する.
/* code: q2-1.c */ #include <stdio.h> int main() { int x, y; printf ("Enter X Value: "); scanf ("%d", &x); printf ("Enter Y Value: "); scanf ("%d", &y); printf ("X = %d\n", x); printf ("Y = %d\n", y); if (x > y) printf ("X is greater than Y.\n"); else printf ("X is less than or equal to Y.\n"); return 0; }
このコードをコンパイルして実行してみる.
$ gcc q2-1.c -o q2-1 $ ./q2-1 Enter X Value: 3 Enter Y Value: 5 X = 3 Y = 5 X is less than or equal to Y. $ ./q2-1 Enter X Value: 1000 Enter Y Value: 10 X = 1000 Y = 10 X is greater than Y.
以下のコードを変更し, 変数 grade が小文字のデータに対しても多分岐出来るようにしなさい.
/* code: ex2-3.c */ #include <stdio.h> int main () { char grade; grade = 'B'; switch (grade) { case 'A': printf ("excellent\n"); break; case 'B': printf ("good\n"); break; case 'C': printf ("fair\n"); break; case 'D': printf ("barely passing\n"); break; case 'F': printf ("not passing\n"); break; default: printf ("ERROR: invalid character\n"); break; } printf ("Your grade is %c\n", grade); return 0; }
このコードをコンパイルして実行する.
$ gcc ex2-3.c -o ex2-3 $ ./ex2-3 good Your grade is B
これを以下のように改修する.
/* code: ex2-3.c */ #include <stdio.h> int main () { char grade; printf ("Enter grade: "); scanf ("%c", &grade); switch (grade) { case 'A': case 'a': printf ("excellent\n"); break; case 'B': case 'b': printf ("good\n"); break; case 'C': case 'c': printf ("fair\n"); break; case 'D': case 'd': printf ("barely passing\n"); break; case 'F': case 'f': printf ("not passing\n"); break; default: printf ("ERROR: invalid character\n"); break; } printf ("Your grade is %c\n", grade); return 0; }
このコードをコンパイルして実行してみる.
$ gcc q2-2.c -o q2-2 $ ./q2-2 Enter grade: a excellent Your grade is a $ ./q2-2 Enter grade: b good Your grade is b $ ./q2-2 Enter grade: F not passing Your grade is F $ ./q2-2 Enter grade: e ERROR: invalid character Your grade is e
以下のコードでは switch 文が使われている. switch 文を使わずに if-else 文で書き換えなさい. 論理演算子 (論理積 &&
, 論理和 ||
, 否定 !
) 等を利用すること.
/* code: q2-3a.c */ #include <stdio.h> int main () { int a; a = 3; switch (a) { case 0: case 1: case 2: printf ("A\n"); break; case 3: case 4: printf ("B\n"); break; default: printf ("ERROR: invalid number\n"); break; } return 0; }
このコードをコンパイルして実行してみる.
$ gcc q2-3a.c -o q2-3a
$ ./q2-3a
B
これを以下のように, まずはファイルの分割を行った.
/* code: q23b.c */ #include <stdio.h> #include "q23b.h" int main () { int num; num = 1; char res; res = *foobar(num); printf ("%c\n", res); return 0; } /* code: q23b.h */ #ifndef Q23B_H #define Q23B_H char *foobar(int); #endif /* code: foobar.c */ # include "q23b.h" char *foobar(int num){ switch (num) { case 0: case 1: case 2: return "A"; break; case 3: case 4: return "B"; break; default: return "ERROR: invalid number"; break; } }
その上で, foobar.c をリファクタリングしていく. リファクタリングするにあたって, Unity を使って以下のようなテストコードを書いた. C 言語のユニットテストには Unity がシンプルで良い感じだったので, 別の機会に詳細に掘り下げる予定.
/* code: tests/test_q23b.c */ #include <stddef.h> #include "vendor/unity.h" #include "../q23b.h" void setUp(void) { } void tearDown(void) { } void test_foobar_0(void) { TEST_ASSERT_EQUAL_STRING("A", foobar(0)); } void test_foobar_1(void) { TEST_ASSERT_EQUAL_STRING("A", foobar(1)); } void test_foobar_2(void) { TEST_ASSERT_EQUAL_STRING("A", foobar(2)); } void test_foobar_3(void) { TEST_ASSERT_EQUAL_STRING("B", foobar(3)); } void test_foobar_4(void) { TEST_ASSERT_EQUAL_STRING("B", foobar(4)); } void test_foobar_5(void) { TEST_ASSERT_EQUAL_STRING("ERROR: invalid number", foobar(5)); } int main(void) { UnityBegin("tests/test_q23b.c"); RUN_TEST(test_foobar_0); RUN_TEST(test_foobar_1); RUN_TEST(test_foobar_2); RUN_TEST(test_foobar_3); RUN_TEST(test_foobar_4); RUN_TEST(test_foobar_5); return (UnityEnd()); }
テストは make で実行出来るように, 以下のような Makefile も用意した.
CFLAGS = -std=c99 CFLAGS += -g CFLAGS += -Wall CFLAGS += -Wextra CFLAGS += -pedantic CFLAGS += -Werror VFLAGS = --quiet VFLAGS += --tool=memcheck VFLAGS += --leak-check=full VFLAGS += --error-exitcode=1 test: tests.out @./tests.out memcheck: tests.out @valgrind $(VFLAGS) ./tests.out @echo "Memory check passed" clean: rm -rf *.o *.out *.out.dSYM *.dSYM tests.out: q23b.c foobar.c tests/vendor/unity.c tests/test_q23b.c @echo Compiling $@ @$(CC) $(CFLAGS) foobar.c tests/vendor/unity.c tests/test_q23b.c -o tests.out build: @$(CC) $(CFLAGS) q23b.c foobar.c -o q23b
試しにテストを走らせてみる.
$ make test Compiling tests.out tests/test_q23b.c:47:test_foobar_0:PASS tests/test_q23b.c:48:test_foobar_1:PASS tests/test_q23b.c:49:test_foobar_2:PASS tests/test_q23b.c:50:test_foobar_3:PASS tests/test_q23b.c:51:test_foobar_4:PASS tests/test_q23b.c:52:test_foobar_5:PASS ----------------------- 6 Tests 0 Failures 0 Ignored OK
いい感じ. 以下のようにリファクタリングを行った.
/* code: foobar.c */ # include "q23b.h" char *foobar(int num){ if (num == 0 || num == 1 || num == 2) { return "A"; } else if (num == 3 || num == 4) { return "B"; } else { return "ERROR: invalid number"; } }
試しにテストを走らせる.
$ make test tests/test_q23b.c:47:test_foobar_0:PASS tests/test_q23b.c:48:test_foobar_1:PASS tests/test_q23b.c:49:test_foobar_2:PASS tests/test_q23b.c:50:test_foobar_3:PASS tests/test_q23b.c:51:test_foobar_4:PASS tests/test_q23b.c:52:test_foobar_5:PASS ----------------------- 6 Tests 0 Failures 0 Ignored OK
いい感じ.
感じたことなど.
台風だったので書きました. / “FTP ユーザーの振る舞いをテストをする rspec-ftp を試した + 抹茶を追加しました” https://t.co/iVfteoT6Bv
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月6日
いろいろと勉強になったのでまとめた.
以前に以下のような記事を書きました.
この時には自前の Ruby スクリプトを使って, ftp ユーザーの振る舞い (ログイン出来るか, chroot になっているか, 読み書き, 削除出来るか等) をチェックしていました.
今回, 以下の Gem を拡張して Rspec を介してチェック出来るようにしてみました.
尚, 自分がチェックしたかった振る舞いに対応するマッチャーが実装されていなかったので, 追加で実装してプルリクエストしています.
マージされると嬉しいなあ.
そもそも, FTP のようないにしへの技術を未だに利用しているのかと突っ込まれそうな気がしていますが, 実際のところ FTP を利用したいというニーズはあります. その中でサーバーの構築というよりは FTP を利用するユーザーの追加や削除という作業に多くの時間を割かれます. 当然, これらの作業はコード化していますが, 作成した FTP ユーザーが正しくログイン出来るか, ファイルの追加や削除は行えるか, 意図しないディレクトリへのアクセスは許可されていないか等を確認した上で依頼主にエビデンスとして共有する必要があると考えています. また, この確認を自作のスクリプトではなく, 既存のテストフレームワーク上で実行することで汎用性を高め, 自作スクリプトという属人化しやすい部分を廃していくことを目的としています.
ということで, 今回は rspec-ftp を利用して FTP サーバーに作成したユーザーをテストする環境を以下のサンプルに用意してみましたので, これを利用して FTP ユーザーの振る舞いテストの雰囲気を紹介致します.
docker-compose up -d
以下のように vsftpd サーバーと Ruby 環境が 3 環境起動します.
$ docker-compose up -d Creating network "rspec-ftp-sample_my_sample_net" with driver "bridge" Creating rspec-ruby23 ... done Creating rspec-ruby25 ... done Creating vsftpd-server ... done Creating rspec-ruby24 ... done
以下のように secret.yml を定義します.
vsftpd-server: users: - username: ftpuser password: supersecret
フォーマットは以下の通りです.
IP アドレス又はホスト名: users: - username: ユーザー名 - password: パスワード
このファイルはリポジトリにうっかりアップしてしまわないように .gitignore に登録しておくと良いでしょう.
$ cat .gitignore secret.yml
テストは rake コマンドを介して実行することを想定している為, rake -T
を実行してタスクの一覧を確認しておきます.
bundle exec rake -T
以下のように出力されることを確認します.
$ docker-compose exec rspec-ruby25 bundle exec rake -T rake ftpcheck:vsftpd-server:ftpuser # Run ftpcheck to vsftpd-server by ftpuser
以下のように rake コマンドを利用してテストを実行します. 一応, Ruby のバージョンに応じて以下のようにコマンドが別れています.
# Ruby 2.5.x 環境で実行する docker-compose exec rspec-ruby25 bundle exec rake ftpcheck:vsftpd-server:ftpuser # Ruby 2.4.x 環境で実行する docker-compose exec rspec-ruby24 bundle exec rake ftpcheck:vsftpd-server:ftpuser # Ruby 2.3.x 環境で実行する docker-compose exec rspec-ruby23 bundle exec rake ftpcheck:vsftpd-server:ftpuser
以下のように出力されることを確認します.
$ docker-compose exec rspec-ruby25 bundle exec rake ftpcheck:vsftpd-server:ftpuser /usr/local/bin/ruby -I/usr/local/bundle/gems/rspec-core-3.8.0/lib:/usr/local/bundle/gems/rspec-support-3.8.0/lib /usr/local/bundle/gems/rspec-core-3.8.0/exe/rspec spec/ftp_spec.rb #be_accessible (real server) can login valid user and password #be_chroot (real server) check chroot enabled #be_writable (real server) check writable with active mode #be_removable (real server) check removable Finished in 0.09052 seconds (files took 0.18992 seconds to load) 4 examples, 0 failures # Summary by Type or Subfolder | Type or Subfolder | Example count | Duration (s) | Average per example (s) | |--------------------|---------------|--------------|-------------------------| | ./spec/ftp_spec.rb | 4 | 0.07874 | 0.01968 | # Summary by File | File | Example count | Duration (s) | Average per example (s) | |--------------------|---------------|--------------|-------------------------| | ./spec/ftp_spec.rb | 4 | 0.07874 | 0.01968 |
いい感じでテストが PASS しました. ちなみに, chroot が適切に設定されていない場合には...
$ docker-compose exec rspec-ruby25 bundle exec rake ftpcheck:xxx.xxx.xxx.xxx:user1 ... Failures: 1) #be_chroot (real server) check chroot enabled Failure/Error: expect(ENV['TARGET_HOST']).to be_chroot.user(property['username']).pass(property['password']) expected "xxx.xxx.xxx.xxx" to be chroot # ./spec/ftp_spec.rb:11:in `block (2 levels) in <top (required)>' Finished in 2.06 seconds (files took 0.20194 seconds to load) 4 examples, 1 failure ...
上記ようにテストはものの見事に失敗します. この時に vsftpd.log を確認すると以下のように上位ディレクトリを参照してしまっていることが確認出来ます.
... Sat Oct 6 15:02:41 2018 [pid 1410] [user1] FTP command: Client "111.222.333.444", "CWD ../" Sat Oct 6 15:02:41 2018 [pid 1410] [user1] FTP response: Client "111.222.333.444", "250 Directory successfully changed." Sat Oct 6 15:02:41 2018 [pid 1410] [user1] FTP command: Client "111.222.333.444", "PWD" Sat Oct 6 15:02:41 2018 [pid 1410] [user1] FTP response: Client "111.222.333.444", "257 "/home"" Sat Oct 6 15:02:41 2018 [pid 1412] CONNECT: Client "111.222.333.444" ...
意図した通りです. これをずーっとやりたかったんです.
実装するにあたって色々と勉強になったことのメモを書いていきます. 色々と気付きがあったので, 思い出したら追記していきたいと思います.
下図の解説が解りやすかった為, 引用させて頂きました.
インターネット・プロトコル詳説(11):FTP(File Transfer Protocol)~後編 より引用.
以前にも rspec のカスタムマッチャの追加方法は勉強しました.
Rspec::Matchers
の define
メソッド内に処理を書いていきます. また, メソッドチェーンは chain
メソッド内にチェーンしたいメソッドを定義していきます.
FTP ユーザー名, パスワードの情報を secret.yml を切り出しておいて, テストで利用する方法の一つとして, Serverspec (厳密には specinfra) の tips で紹介されている property と set_property メソッドを spec_helper 内で利用してみました.
require 'rspec' require 'rspec-ftp' require 'specinfra/properties' require 'yaml' def property Specinfra::Properties.instance.properties end def set_property(prop) Specinfra::Properties.instance.properties(prop) end properties = YAML.load_file('secret.yml') host = ENV['TARGET_HOST'] ftpuser = ENV['FTP_USER'] set_property properties[host]['users'].first {|u| u['username'] == ftpuser }
元々の rspec-ftp にもテストは書かれていましたが, 今回いくつかのマッチャを追加するにあたり, Docker コンテナで立てた FTP サーバーに対してテストを実行したいと考えました. このような場合, docker-compose を利用するのが良いと考えていますが, 今まで Travis CI で docker-compose を使えることを知りませんでした.
ところが, 確認したところ, ずいぶん前から docker-compose は利用可能な状態になっていたことが判ったので, 以下のような docker-compose.yml を作成してテストを行うようにしました. 特に Travis CI で実行する為に特別な設定は行っておらず, 手元の端末でも docker-compose up -d
で一発起動します.
version: "2" services: vsftpd-server: image: fikipollo/vsftpd container_name: vsftpd-server environment: - FTP_USER=ftpuser - FTP_PASS=supersecret - ONLY_UPLOAD=NO - PASV_ENABLE=YES - PASV_ADDRESS=172.26.0.6 - PASV_MIN=21200 - PASV_MAX=21210 ports: - "21:21" - "21200-21210:21200-21210" networks: my_sample_net: ipv4_address: 172.26.0.6 ... 略 ... rspec-ruby23: image: ruby:2.3 build: ./spec/docker/rspec container_name: rspec-ruby23 volumes: - .:/work command: tail -f /dev/null networks: my_sample_net: ipv4_address: 172.26.0.3 networks: my_sample_net: driver: bridge ipam: driver: default config: - subnet: 172.26.0.0/16 gateway: 172.26.0.1
networks
を利用しているのは, vsftpd で PASV_ADDRESS
の IP アドレスをコンテナの IP アドレスに固定する必要があった為です.
複数の Ruby バージョンを使って並列してテストを走らせる為には, matrix を利用して, 環境変数 TEST_TARGET
に対象の Ruby バージョンコンテナ名を入れてあげれば良いようです. 以下は実際に利用している .travis.yml です.
sudo: required matrix: include: - name: "Ruby 2.5" env: TEST_TARGET=rspec-ruby25 - name: "Ruby 2.4" env: TEST_TARGET=rspec-ruby24 - name: "Ruby 2.3" env: TEST_TARGET=rspec-ruby23 services: - docker before_install: - docker-compose build ${TEST_TARGET} - docker-compose up -d install: before_script: script: - docker-compose exec ${TEST_TARGET} rspec after_script: notifications:
これを利用することで, 下図のように複数の Ruby バージョンにおいて並列でテストが走ることを確認しました.
FTP という由緒正しいいにしへの技術が今後も使われ続けるのであれば, rspec-ftp を通して FTP について理解を深めていく必要があると感じました. また, 今回のようにインフラの振る舞いをテストするツールとしては infrataster と連携 (もしくはプラグイン化) 出来ないか考えてみたいと思います.
奥さん, 12 回目もやるんですって. / JAWS-UG 福岡 もくもく会 #12 https://t.co/o2vbqrgd73 #jawsug #jawsugfuk
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月4日
12 回目もやります.
台風が直撃しそう.
新しい MacOS の名前は「モハベ」と読むらしい. ずっと「モジャベ」って思っていましたのであります. 髪の毛がもじゃもじゃ毛. #jawsugfuk #jawsug
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月4日
「もじゃべ」じゃないらしい.
今更次郎かもしれないけど, Travis CI で docker-compose が使えるという知見を得た. #jawsugfuk
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月4日
夢が広がる. ただ, Ruby の複数のバージョンを使ったテストとかしたい場合どうするんだろう.
「なぜ便利に使えているのか?」を理解すること。 / “Fukuoka.rb #108 を開催した - 虚無庵” https://t.co/yQxNLkK8bf
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月4日
そう言えば, もくもく会でも似たような話が出た. コンテナを使ったことが無いという層に対して k8s のハンズオンをしても響かない. ちゃんとコンテナを理解すべきだという話をされていて, ホンコレ, なぜ k8s だけが一人歩きしているんだろうなあと改めて考えてしまった. ま, 自分はコンテナもちゃんと理解出来ていないし, k8s なんて全く解ってないのでこれから勉強しなきゃという感じ.