Vimのカバレッジを見る

この記事はVim Advent Calendar 2017の2日目の記事です。

Vimカバレッジcoveralls.iocodecov.io上で見ることができますが、手元で見たくなることもあると思います。

そんな時はMakefileに書いてある通り、次のようにしてカバレッジを計測し、生成されたhtmlから見ることができます。

# Vimのリポジトリ直下に移動

# 各種フラグに--coverageをつけてビルド、他はお好みで
CFLAGS=--coverage LDFLAGS=--coverage ./configure --with-features=huge && make

cd ./src

# まずはゼロカバレッジの初期データを作る
lcov -c -i -b . -d objects -o objects/coverage_base.info

# テストを走らせてカバレッジ情報を作る
make test
lcov -c -b . -d objects/ -o objects/coverage_test.info

# 初期データとテストのカバレッジ情報を結合する
lcov -a objects/coverage_base.info -a objects/coverage_test.info -o objects/coverage_total.info

# 結果をobjects/index.htmlとして生成する
genhtml objects/coverage_total.info -o objects

ビルド時に--coverageをつけることでgcovが有効になり、実行された行が記録されるようになります。
lcov(と付属のgenhtml)はそのフロントエンドツールです。

さて、実行された行が記録されるということは、Vimで特定の操作をした際にどの関数が呼ばれたのか調べることができる、ということです。

しかし、gcovはプログラムの終了直前に保存処理を行うため、目的の操作だけの結果を知るには

  1. Vimの起動
  2. コマンドやキー入力
  3. Vimの終了

の順で操作をしつつ、1と3は除外する必要があります。
幸いgenhtmlには-b(--baseline-file)オプションがあるので次のようにすれば実現できます。

# 上と同じ
lcov -c -i -b . -d objects -o objects/coverage_base.info

# 起動と終了のカバレッジを取得
VIMRUNTIME=../runtime ./vim --clean -c 'q'
lcov -c -b . -d objects/ -o objects/coverage_quit.info
lcov -a objects/coverage_base.info -a objects/coverage_quit.info -o objects/coverage_baseline.info

# カバレッジをリセット
lcov -z -d objects/

# 起動と:smileと終了のカバレッジを取得
VIMRUNTIME=../runtime ./vim --clean -c 'smile | q'
lcov -c -b . -d objects/ -o objects/coverage_smile.info
lcov -a objects/coverage_base.info -a objects/coverage_smile.info -o objects/coverage_result.info

# 生成されるのはcoverage_result.infoからcoverage_baseline.info分のカウントを減らした結果
genhtml -b objects/coverage_baseline.info objects/coverage_result.info -o objects

というわけで:smile*1にはsyntax.c以下のソースが使われていることがわかりました。

f:id:daisuzu:20171202170437p:plain

処理を正確に追うにはデバッガを使うのが確実だとは思いますが、ソースの構造がある程度わかっていないと難しかったりもします。

f:id:daisuzu:20171202173329p:plain

なので、さらっと概要と知りたい時やソースコードリーディングのお供にでも是非カバレッジを活用してみてください。

:smile
                            oooo$$$$$$$$$$$$oooo
                        oo$$$$$$$$$$$$$$$$$$$$$$$$o
                     oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o         o$   $$ o$
     o $ oo        o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o       $$ $$ $$o$
  oo $ $ "$      o$$$$$$$$$    $$$$$$$$$$$$$    $$$$$$$$$o       $$$o$$o$
  "$$$$$$o$     o$$$$$$$$$      $$$$$$$$$$$      $$$$$$$$$$o    $$$$$$$$
    $$$$$$$    $$$$$$$$$$$      $$$$$$$$$$$      $$$$$$$$$$$$$$$$$$$$$$$
    $$$$$$$$$$$$$$$$$$$$$$$    $$$$$$$$$$$$$    $$$$$$$$$$$$$$  """$$$
     "$$$""""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     "$$$
      $$$   o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     "$$$o
     o$$"   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$       $$$o
     $$$    $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" "$$$$$$ooooo$$$$o
    o$$$oooo$$$$$  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$   o$$$$$$$$$$$$$$$$$
    $$$$$$$$"$$$$   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     $$$$""""""""
   """"       $$$$    "$$$$$$$$$$$$$$$$$$$$$$$$$$$$"      o$$$
              "$$$o     """$$$$$$$$$$$$$$$$$$"$$"         $$$
                $$$o          "$$""$$$$$$""""           o$$$
                 $$$$o                                o$$$"
                  "$$$$o      o$$$$$$o"$$$$o        o$$$$
                    "$$$$$oo     ""$$$$o$$$$$o   o$$$$""
                       ""$$$$$oooo  "$$$o$$$$$$$$$"""
                          ""$$$$$$$oo $$$$$$$$$$
                                  """"$$$$$$$$$$$
                                      $$$$$$$$$$$$
                                       $$$$$$$$$$"
                                        "$$$""""

Press ENTER or type command to continue

*1:v8.0.1362

VimConf 2017でコントリビューターになる話をしてきた

VimConf 2017 - An international Vim Conference

speakerdeck.com

という発表をしてきました。

最初は25分も話すことなんてあるかな?なんて思ってたら意外と良い感じの時間になりました。
少し足りなかったところを少し補足しておくと、

gitコマンド

  • v8.0.0512の調査で使ったgit bisect
# <bad> <good>の順番で指定する
git bisect start v8.0.0104 v8.0.0000

あとは都度makeし、手動で補完させてgoodかbadを判定していきました。
※コマンドからの補完だと再現しなかったため

活動時間帯について

  • 不具合の発見〜1次調査・応急処置
    • 職場
  • 2次調査〜パッチ作成

patchを送った後について

  • 質問とかツッコミは無かったの?
    • 全く無かった
    • パッチが小さかったからかも
    • もしくはタイミングが良かったからとか

場合によってはなかなか取り込まれないこともあったりするようです。

MacVimの不具合って?

マルチバイト文字が潰れて表示されてしまうという問題を修正しました。 github.com

コントリビュートする内容について

  • 新機能を追加したいんだけど
    • 色々大変だと思いますが、新機能を作れるくらいのパッションがあれば大丈夫だと思います!
  • ドキュメントや軽微な(typo・インデント)修正でも良いの?
    • OKです!

ちなみにコントリビュートに対するスタンスは最近聴いた

tech.gunosy.io

にすごく共感するものがあります。

vim-jpについて

vim-jpに気軽に相談してみましょう」なんて勝手に言ってしまって良いのかな?
とか思っていたんですが、なんと本日vim-jpのメンバー入りをしました。
もし何かあれば出来る範囲でサポートしていきたいです!
ということを本番でも話せれば良かったんですが、アドリブ力が無く。。。

     

という感じです。

スライドの英訳を助けてくれた ujihisa さん、
発表の翻訳をしてくださった sandkatt さん、
そしてスタッフのみなさま、
ありがとうございました!お疲れ様でした。

プレゼン資料にハイライトされたコードを貼り付ける

プレゼン資料を作っててコードを綺麗に表示させたくなったんだけど、 何かをインストールしたりとかは面倒だったのでvim*1を使ってやることにした。

以下のコマンドでブラウザが開くのでコピペするだけ。

" mac
:TOhtml | w | !open %

" win
:TOhtml | w | !start %

" linux
:TOhtml | w | !xdg-open %

調べてみたらプラグインが見つかったけどmac用だしインストールが面倒なので試していない…

github.com

*1:自分が使うPCには常にインストールされているし

Meguro.vim #4でプラグインを作った

Meguro.vim #4で自分用の:VimFilerSimpleに代わるプラグインを作りました。

github.com

vimfilerは機能が豊富でそこまで不満があったわけではないのですが、

  • 常に全ての機能を必要としているわけではない
  • unite.vimに依存している

ので、一番使う頻度の高い ファイルをツリー形式で表示する だけのプラグインが欲しかったからです。

f:id:daisuzu:20170715163006p:plain

実装としてはtreeコマンドの結果を

というものです。

マッピングは一切用意していないため、ファイルを開く時には標準機能のgfなどを使うことになります。
また、プラグインの起動もTreeコマンドしか提供していないため、使い方に合わせたマッピングをvimrcに追加していくというデザインにしています。

" Example:

" 垂直分割してツリーを表示し、ウィンドウの幅を32にする
nnoremap <silent> <Leader>vt
      \ :<C-u>execute 'vertical '. v:count .'Tree'
      \ <Bar> vertical resize 32
      \ <CR>

" ツリーを閉じずにファイルを開く
nnoremap <silent> <C-w>e
      \ :<C-u>let @a = fnameescape(expand('<cfile>'))
      \ <Bar> wincmd w 
      \ <Bar> execute 'edit ' . @a
      \ <CR>

grpc-web-clientをjsで試してみた

gRPC-Web: Moving past REST+JSON towards type-safe Web APIs - Improbableを見て、grpcwebを使えばgoogle.golang.org/grpc製の既存gRPCサーバがブラウザからも叩けるようになるとのことなので試してみた。

github.com

サーバ側の変更点

DOC.mdにも書いてあるように

の2点を行うだけ。

diff --git a/backend/main.go b/backend/main.go
index 0f230c4..f261ab9 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -2,8 +2,9 @@ package main
 
 import (
    "log"
-  "net"
+   "net/http"
 
+   "github.com/improbable-eng/grpc-web/go/grpcweb"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
@@ -30,11 +31,15 @@ func main() {
    gs := grpc.NewServer(opts...)
    pb.RegisterEchoServer(gs, &server{})
 
-  l, err := net.Listen("tcp", addr)
-  if err != nil {
-      log.Fatal(err)
+   ws := grpcweb.WrapServer(gs)
+
+   mux := http.NewServeMux()
+   mux.Handle("/", http.HandlerFunc(ws.ServeHttp))
+   hs := &http.Server{
+       Addr:    addr,
+       Handler: mux,
    }
 
-  log.Println("Starting server on", l.Addr())
-  log.Println(gs.Serve(l))
+   log.Println("Starting server on", hs.Addr)
+   log.Println(hs.ListenAndServeTLS("./certs/cert.pem", "./certs/key.pem"))
 }

ブラウザ側の実装

サンプルはTypeScriptだけど、JavaScriptでも書けるみたいなのでES6でやってみた。

1. まずはprotocのプラグインts-protoc-genをインストールする
npm install --save-dev ts-protoc-gen

これで以下のように.protoからJavaScriptの定義ファイルが生成できるようになる。

protoc --plugin=protoc-gen-js_service=./node_modules/.bin/protoc-gen-js_service \
--js_out=import_style=commonjs,binary:. \
--js_service_out=. \
pb/*.proto

js_outで生成されるのが.protoのmessage
js_service_outで生成されるのが.protoのserviceになっていた。

2. 次にクライアントライブラリのgrpc-web-clientをインストールする
npm install --save google-protobuf @types/google-protobuf grpc-web-client

使い方はgrpc-web-clientのinvoke()に第1引数としてjs_service_outのrpc、第2引数としてリクエスト、接続先、各種コールバック関数をオブジェクトで渡す形になる。

import {grpc} from "grpc-web-client";

import {Echo} from "../pb/echo_pb_service.js";
import {Request} from "../pb/echo_pb.js";

function EchoCall(value) {
  const req = new Request();
  req.setValue(value);

  grpc.invoke(Echo.Call, {
    request: req,
    host: "https://localhost:9090",
    onMessage: (message) => {
      console.log("onMessage", message.toObject());
      alert(message.getValue());
    },
    onEnd: (code, msg, trailers) => {
      console.log("onEnd", code, msg, trailers);
    }
  });
}

// ちゃんと動けば引数の文字列がダイアログに出てくる
EchoCall("Hello grpc-web-client");

リクエストやレスポンスなどの各フィールドには基本的にgetterとsetterを通してアクセスすることになるみたい。

既存クライアントへの影響

気になるのは既存クライアントがどうなるのかというところ。
grpc.DialOptionの認証情報の有無で見てみると次のような結果になった。

grpc.WithInsecure()
メソッド \ サーバオプション 無し grpc.Creds()
ListenAndServe() X X
ListenAndServeTLS() X X
grpc.WithTransportCredentials()
メソッド \ サーバオプション 無し grpc.Creds()
ListenAndServe() X X
ListenAndServeTLS() O O

つまりクライアントはgrpc.WithTransportCredentials()が必須になり、grpc.Serverのオプションに関わらずListenAndServeTLS()を使えば良いということになる。
(grpc.WithInsecure()を使っていたらクライアントを直すなりサーバを分けるなりしないといけない)

まとめ

サーバ側はけっこう簡単にブラウザ対応できるし、
grpc-gatewayと比べると

が不要になるので管理するものが減って少し楽になりそう。

けど証明書が必要になるのはローカルでの動作確認とかがちょっと面倒になるかも…

まあフロントエンド的にはswaggerで生成するかprotocで生成するかの違いなので実際どっちでも良かったりするのかな?

今更だけどLGL22をIIJmioで使い始めた

LGL22はそこそこ古い機種で、もう3年くらい使っているけど
色々とあって未だにこれを使っている。 www.lg.com

理由の1つとしてIIJmioのタイプAを待っていたっていうのもあるんだけど、
タイプAはVoLTE対応機じゃないと使えないので非対応のLGL22はアウツ…

なので結局タイプDにすることにした。

流れとしては、

1. MNP予約番号の取得

0077-75470 に電話してMNP予約番号を発行してもらう。
電話で伝えてもらえるけど後からSMSも送られてくるのでメモとか無くても大丈夫。

2. SIMロックの解除

以下のサイトからアンロックコードを取得する。

https://sim-unlock.net/jp/simlock/LG/LGL22/

画面には*#06#でIMEIを調べてくださいと書いてあったけど1桁足りないので、

設定 → 一般 → 端末情報 → ステータス

に表示されているIMEIを入力した。
たしか入力してから5分くらいでメールが送られてきたと思う。

送られてきたNCKは

2945#*22# -> ネットワークロック

から入力する。

3. IIJmioの申し込み

普通にWebから申し込む。
SIMカードのサイズはnanoSIMを選択。

4. 回線の切り替え

実際にSIMが届いたのは申し込みから3日後だった。
IIJmioオンデマンド開通センター」に電話して回線を切り替える。
時間が19:00までなのに気づいたのが18:56だったけどなんとか間に合った。

その後は

3845*#22# -> KDDI Only -> Network Setting -> Network Mode Change

からLTE/CDMA/GSM/WCDMAを選択して、

設定 -> テザリングとネットワーク -> モバイルネットワーク -> アクセスポイント名 -> 手動設定

からAPNを設定する。
いつ切り替わったか覚えていないけど10分後には使えるようになっていた。
電話の発着信も問題無し。

2週間ちょい使ってみて

Band1 Onlyなのが少し不安だったけど都内で生活する分には全然問題無かった。
クーポンOFFだと使い物にならない時もあるけど今の所そこまで困っていない。

エンジニアにジョブチェンジしてやってきたこと

「君のスキルはウチの新人と同レベルだけど、そんなんでやっていけるの?」と言われて今の会社に入社することを決めたのはもう4年半ほど前のこと。
前職はテスターでコードは全然書けなかったしデータベースとかも触ったことなかったけど好き勝手やらせてもらった結果、それなりのエンジニアに成長することができたと思う。
それも今月いっぱいで退職なのでちょうど良い機会だし自分じゃないとできなかった(やらなかった)ようなことを中心にざっくり振り返ってみる。
※イマイチだったことは全部書いていくとキリがないので省略

Perl(CGI + 生DBI)製Webシステムの改善

Template-toolkitをwrapするPerlモジュールを作った
  • HTMLがscriptタグも含めて1行ずつ丁寧にprintされていたのでメンテナンスが辛かった
  • SQLも文字列連結で組み立てられていたり、無理矢理1行に詰め込まれていたりしたのでメンテナンスが辛かった
  • Perlのバージョンが古くて(たしか5.8)フレームワークの導入も難しい状況だったのでとりあえずTemplate Toolkitで凌ぐことにした
    • HTMLは標準出力に吐き出してテンプレート化
    • SQLVim scriptで.cgiから抽出してテンプレート化
  • Perlモジュールにしたのは各.cgiで統一的なコードが書けるようにしたかったから
DBアクセス用のPerlモジュールを作った
  • DBIの初期化を各.cgiでやっていたのでPerlモジュールに切り出した
  • よく使われていた処理もまとめてPerlモジュールに持っていった
  • 引数でテストモードを指定するとTest::mysqldに繋がるようにした
    • テストコードが一切無かったのでテストを書く布石にしたかった
単体テストを書くようにした
  • ↑の通り、テストが無かったのでTest::Moreでテストを書くようにした
    • その前に.cgiは関数化されていなかったので関数化するところから始めた
    • strictをつけると動かなくなるコードもたくさんあったので合わせて直していった
  • DB関連のテストには↑のモジュールとTest::Fixture::DBIを使った

新しい言語・フレームワークの導入

Python(Flask + SQLAlchemy)
  • Perlで各自が好き勝手にコードを書いているとメンテナンスが辛くなるというのがチームの共通認識だった
    • 当時はコードレビューをするという文化も無かったし…
  • チーム内でPythonに興味を持っている人が多かったので新規システムを作るタイミングで思い切って導入してみた
  • DjangoPyramidを使わなかった理由としては、
    • 今までがcgiオンリーだったので重量級フレームワークに慣れている人がいなかった
    • ロックインされたくなかった
    • といったところ
  • py.testでテストし、pep8pyflakesでコードのチェックをするようにした
  • 今まではテーブルの管理を一切していなかったのでalembicFlask-Migrateで管理するようにした
Go
  • ↑でPythonを導入したはいいけどほとんどがCentOS6上の2.6で動いているのでなんとかしたくなった
    • 最初は2.7や3系への移行を検討したけど、OSのデフォルトではないバージョンを使うのに反対する人が多かった
      • そうこうしているうちに2.7も終わりが見えてきてしまったし…
    • パッケージの管理が不十分でリリース後にImportErrorで落ちることが多々あった
      • pipではなく、yumを推奨しているチームもあったのでバージョン違いでうまくいかなかったりすることも…
    • テストやコードチェックを実施しない人が増えてきた
      • 理由としてはパッケージのインストールができないとかIDEが対応していないとか…
        • リリース後のトラブルは明らかに増えていたけど気にする人は少なかった
  • ということで、ある時期から新規システムを作るときはGoを使うようにしてみた
  • フレームワーク
  • 外部パッケージはgit subtreeでvendoringするようにした

ログ・監視

fluentdでログ収集とアラート検知をするようにした
  • 今まではアプリケーションコード内でアラートの処理を行なっていた
    • アラートの実装が漏れたり後回しにされることがあった
    • アラート処理自体に問題があってシステムが落ちてしまうこともあった
  • アプリケーション側は適切なログレベルでログを出力するだけにし、その後の処理は全てfluentdに任せるようにした

リポジトリ

Trac(svn)からGitHub Enterpriseに移行した
  • 積極的に移行したいという人は自分しかいなかったけど反対意見はあまり気にせず移行することにした
    • 自分にとっては複数人で開発するのにsvnだと厳しかった
    • 反対意見としては
      • Tracでも困らないとか
      • Tracでgitを使えば良いとか
      • gitを覚えたくないとか
        • こういう人にはなんとか覚えてもらうことにした
  • 移行後はPull Requestでコードレビューをする文化も少しずつだけど作っていった
drone.ioの導入
  • GitHub Enterpriseに移行したリポジトリでは以下のようなことをdrone.ioで実行するようにした
    • Lint
    • 単体テスト
      • Goは自作ツールでテストを回した
    • バイナリのビルド
    • ドキュメント(Sphinx)のビルド
    • Pull Requestの自動レビュー

デプロイ

fabricの導入
  • 当初は全てwikiなどに記載された手順書を頼りにコマンドを1つずつ打ち込んでいた
    • 設定ファイルの変更はvi /etc/my.cnfのような手順になっていることもあった
  • 最初に担当したシステムで耐えられなくなったのでfabric + shell scriptでデプロイをするようにした
    • ansibleを使わなかったのは間にPython2.5のホストがあって使えなかったから
    • chefを使わなかったのは
      • 対象ホストにインストールできないと思っていたから
      • ローカルにあるファイルを対象ホストに転送する方法がわからなかったから
chef-soloの導入
  • fabricだと共通処理も含めて毎回コピペして設定を作っていたのでBerkshelfが使いたくなった
  • 実はホストの手配をした際にchefがインストールされているということを知った
  • ファイル転送はfabricを使いつつ、ホストの設定にはchef-soloを使うようにした
自作デプロイツールの導入
  • 最初の頃に比べたらデプロイがだいぶ楽にはなったけどいくつか問題が出てきた
    • 踏み台ホストを経由するのでファイル転送に時間がかかる
    • rootになれる人しか使えないので特定の人に負荷が集中してしまうことがあった
      • 制限していたのはホストの状態を誰も把握できなくなってしまうと困るから
  • そこで対象ホストで実行するタイプのツールを新たに作成し
    • ファイル転送は対象ホストから直接gitで取得するようにした
      • 合わせてビルド済みのバイナリやDockerのイメージもダウンロードする
    • チームのメンバなら誰でも実行できるようにし、オペレーションを全自動で行うようにした
      • 開発時はアプリケーションコードだけでなく、オペレーションも含めてレビューをする
      • タグがついた最新のバージョンのみがデプロイされるようにしたので
        • 同じバージョンは1回しかデプロイされないし巻き戻ることはない
        • バージョンがわかればホストの状態も把握できる

こうしてみると改善系の活動を結構やってきたんだけど、それが偉い人にはあまり評価されなかったのは残念といえば残念。
というか2つ上の職位に要求されることが出来ていないからといって評価を下げられたのは正直根に持ってる。 あとは実際の新人に入社時の自分と同じレベルのことを求めたら何度か怒られてしまったことがあるのも今となっては良い思い出かな?