2017年8月8日火曜日

[CI] Scrutinizer 始めました



今回紹介する CI サービスは Scrutinizer です。
Pricing をみるとフリープランはないように見えますが、OSS であればフリーで使えるようです

今回、使い始めてから紹介できるようになるまでにかなーりの年月を費やしてしまいましたので、Sign up まわりは省略させていただきます。
(C++非サポート&Python のテスト周りで手間取った)

Scruntinizer の魅力
Scruntinizer の魅力は登録するだけですぐに静的解析の結果が得られるところです。
Scruntinizer は静的解析などの検証に特化した CI サービスで、めんどうな設定や YAML を書かずにすぐに始められます。テストもリポジトリを探索して自動で実行してくれます。

あと、レポートもグラフィカルでいい感じです。


使用例
いつもであれば、iutest の使用例で紹介するのですが、C++ は非サポートなので、
今回は「ブログズミ: ソースコード中の単語からの略語/スペルミス検出に挑戦」で作成した
taggertool リポジトリで紹介します。

リポジトリ登録
まずは、リポジトリの登録から始めます。
「Add Repository」ボタンを押すと、以下のような画面になるので、CI したいリポジトリを入力、言語を選択して、登録します。


リポジトリは、Github/Bitbucket/GitLab/PlainGit から選択できます。
Plain Git が可能なので、独自リポジトリが使えるのは良いところだと思います。

登録は以上です。
完了すると最初のテストが実行されます。

taggertool の結果
taggertool の結果はこんな感じです。

サマリー


Issues に、各静的解析で出た結果が出ます。


Code にはクラスおよび関数のサイズと複雑度がリストアップされます。


Inspections はキックされたジョブが並びます。


Reports には最後に実行されたジョブの結果が表示されます。


最後に、Statistics には Quality Rating や issue などの推移がグラフで確認できます。
Quality Rating は上の図で 9 となっているやつで、コードの複雑度、結合度など複数の要素から1つのスコアにしたもので、10点満点で評価されます。


チェック項目を増やす
さて、Scrutinizer には様々なツールで様々な解析ができる準備がされていますが、デフォルトでは多くのチェックは無効になっています。
チェックを有効にするには、Settings の Configuration に Checks ボタンがあるので、そこを開きます。


開くと Not Enabled なチェック項目がリストアップされているので、
必要なものにチェックを入れてください。


チェックの有効化は Congiguration の Repogitry Config や yml に直接書くこともできます。
チェックしたい項目が多い場合は、(UI でチェックするのが面倒なので)こちらの方がよいでしょう。

また、グローバルな設定も作成できるので、
複数プロジェクトで同じチェックをさせたい場合は、そちらを使うと良さそうです。
checks:
    python:
        code_rating: true
        duplicate_code: true
        variables_used_before_assignment: true
        variables_unused_wildcard_import: true
        variables_unused_variable: true
        variables_unused_import: true
        variables_unused_argument: true
        variables_unpacking_non_sequence: true
        variables_undefined_variable: true
        variables_undefined_loop_variable: true
        variables_undefined_all_variable: true
        variables_unbalanced_tuple_unpacking: true
        variables_redefined_outer_name: true
        variables_redefined_builtin: true
        variables_redefine_in_handler: true
        variables_no_name_in_module: true
        variables_invalid_all_object: true
        variables_global_variable_undefined: true
        variables_global_variable_not_assigned: true
        variables_global_statement: true
        variables_global_at_module_level: true
        typecheck_unexpected_keyword_arg: true
        typecheck_too_many_function_args: true
        typecheck_redundant_keyword_arg: true
        typecheck_not_callable: true
        typecheck_no_value_for_parameter: true
        typecheck_no_member: true
        typecheck_missing_kwoa: true
        typecheck_maybe_no_member: true
        typecheck_duplicate_keyword_arg: true
        typecheck_assignment_from_none: true
        typecheck_assignment_from_no_return: true
        string_truncated_format_string: true
        string_unused_format_string_key: true
        string_too_many_format_args: true
        string_too_few_format_args: true
        string_mixed_format_string: true
        string_missing_format_string_key: true
        string_format_needs_mapping: true
        string_constant_anomalous_unicode_escape_in_string: true
        string_bad_str_strip_call: true
        string_constant_anomalous_backslash_in_string: true
        string_bad_format_string_key: true
        string_bad_format_character: true
        open_mode_bad_open_mode: true
        miscellaneous_fixme: true
        newstyle_bad_super_call: true
        logging_unsupported_format: true
        logging_too_many_args: true
        logging_too_few_args: true
        logging_not_lazy: true
        logging_format_truncated: true
        imports_wildcard_import: true
        imports_relative_import: true
        imports_reimported: true
        imports_import_self: true
        imports_import_error: true
        imports_deprecated_module: true
        imports_cyclic_import: true
        format_unnecessary_semicolon: true
        format_trailing_whitespace: true
        format_superfluous_parens: true
        format_old_ne_operator: true
        format_multiple_statements: true
        format_mixed_indentation: true
        format_missing_final_newline: true
        format_lowercase_l_suffix: true
        format_line_too_long:
            max_length: '100'
        format_bad_whitespace: true
        format_bad_indentation:
            indentation: '4 spaces'
        format_backtick: true
        exceptions_raising_non_exception: true
        exceptions_raising_string: true
        exceptions_raising_bad_type: true
        exceptions_pointless_except: true
        exceptions_notimplemented_raised: true
        exceptions_catching_non_exception: true
        exceptions_broad_except: true
        exceptions_binary_op_exception: true
        exceptions_bare_except: true
        exceptions_bad_except_order: true
        design_interface_not_implemented: true
        design_abstract_class_not_used: true
        design_abstract_class_little_used: true
        classes_valid_slots: true
        classes_super_init_not_called: true
        classes_signature_differs: true
        classes_protected_access: true
        classes_non_parent_init_called: true
        classes_non_iterator_returned: true
        classes_no_self_use: true
        classes_no_self_argument: true
        classes_no_method_argument: true
        classes_no_init: true
        classes_missing_interface_method: true
        classes_method_hidden: true
        classes_interface_is_not_class: true
        classes_bad_staticmethod_argument: true
        classes_bad_mcs_method_argument: true
        classes_bad_mcs_classmethod_argument: true
        classes_bad_context_manager: true
        classes_bad_classmethod_argument: true
        classes_attribute_defined_outside_init: true
        classes_arguments_differ: true
        classes_access_member_before_definition: true
        classes_abstract_method: true
        basic_yield_outside_function: true
        basic_useless_else_on_loop: true
        basic_unreachable: true
        basic_unnecessary_pass: true
        basic_unnecessary_lambda: true
        basic_star_args: true
        basic_return_outside_function: true
        basic_return_in_init: true
        basic_return_arg_in_generator: true
        basic_pointless_string_statement: true
        basic_pointless_statement: true
        basic_old_raise_syntax: true
        basic_not_in_loop: true
        basic_nonexistent_operator: true
        basic_missing_reversed_argument: true
        basic_missing_module_attribute: true
        basic_missing_docstring: true
        basic_lost_exception: true
        basic_init_is_generator: true
        basic_function_redefined: true
        basic_expression_not_assigned: true
        basic_exec_used: true
        basic_eval_used: true
        basic_empty_docstring: true
        basic_duplicate_key: true
        basic_duplicate_argument_name: true
        basic_dangerous_default_value: true
        basic_bad_reversed_sequence: true
        basic_assert_on_tuple: true
        basic_abstract_class_instantiated: true

最後に
Scruntinizer は、最初から静的解析ツールが用意されており、それを目的にするのであれば、
通常の CI サービスよりも、とても簡単に恩恵が得られるのでオススメです。
(C++ がサポートされるともっとステキですが…あ、あと Scruntinizer のスペルが全然覚えられない…

今回は Python で利用しましたが、他の言語で何か作るときは、また利用したいなと思います。

以上。


2017年7月31日月曜日

続・ソースコード中の単語からの略語/スペルミス検出に挑戦

ブログズミ: ソースコード中の単語からの略語/スペルミス検出に挑戦」で紹介した略語チェッカーを大幅に更新したので、続報です。
ついでなので、Glosbe でリクエスト制限にかかってしまった場合の対処方法も紹介しておきます。

変更点
usage: abbreviation.py [-h] [-v] [-g FILE] [-w FILE] [-e EXCLUDE]
                       [-a ABBREVIATION] [--glosbe] [--dejizo] [--cache]
                       [--load-cache NAME] [--cache-dir DIR]
                       [--disable-keywords] [-x {c++,c#,objc,diff}]
                       [--list-all] [--progress] [--safe-mode]
                       [--encoding ENCODING] [--extension EXTENSION]
                       [--ignore-noexists] [--relpath] [--word WORD] [-]
                       [FILE/DIR [FILE/DIR ...]]

positional arguments:
  FILE/DIR              source code file/dir

optional arguments:
  -h, --help            show this help message and exit
  -v, --version         show program's version number and exit
  -g FILE, --gene FILE  exlude word
  -w FILE, --whitelist FILE
                        whitelist file
  -e EXCLUDE, --exclude EXCLUDE
                        exlude word
  -a ABBREVIATION, --abbreviation ABBREVIATION
                        abbreviation word
  --glosbe              use online translation service (glosbe)
  --dejizo              use online service (dejizo)
  --cache               online translation cache enable
  --load-cache NAME     load translation cache
  --cache-dir DIR       translation cache directory
  --disable-keywords    disable general and language keywords
  -x {c++,c#,objc,diff}, --language {c++,c#,objc,diff}
                        select language
  --list-all            list up all location
  --progress            print percent progress
  --safe-mode           api request limit safe mode(glosbe)
  --encoding ENCODING   set file encoding
  --extension EXTENSION
                        file extension matcher
  --ignore-noexists     ignore option file not exists
  --relpath             print relative path
  --word WORD           dircet check words
  -                     source code from stdin

diff ファイルに対応
便利になった機能の1つがこれだと思います。
diff ファイルから差分があったところだけをチェックするので、コードレビューのときに重宝します。
対応しているフォーマットは Unified format になります。

略語判定ロジックの大幅更新
「~の略語」だけでなく、「~の過去形」「~の複数形」なども検出して、辞書判定するように改善しました。主に、boost のヘッダーファイルをチェッカーに入力して、その結果を見ながら、期待に沿わないものに対応していった感じです。

ただ、ソースコード中で出てきた場合の一般的な意味と、辞書に載っている意味で剥離している場合もあります。
例えば、「apis」とあったら、「APIs」のことだとプログラマーは思うと思うのですが、辞書的には「蜂」です。
こういったものを機械的に振り分けるのは(今の自分の力量では)難しかったので、100% プログラマーが期待する結果になるわけではありませんので、ご注意!
ディープラーニングさせたらいいのかな?

ともあれ、boost を解析して自分なりに納得のいくところまで対応できたかなーと思ってます。

Glosbe でリクエスト制限にかかってしまった場合の対処方法
('Http error:', u'429 Client Error:  for url: https://glosbe.com/gapi/translate?dest=ja&phrase=opendir&from=en&pretty=true&format=json')
('request count: ', 802)
Please access the glosbe, click the search button and check reCAPTCHA.
Glosbe でリクエスト制限にかかると上記ログが出力されます。
これが出た場合、以下の方法で回復が可能です。

まずは、https://ja.glosbe.com/ にアクセスして、普通に辞書検索します。


以下のような画面になるので、チェックをクリアすると制限が解除されます。
(何回も制限かけられましたが、今のところこの方法ですぐに解除できています。)


最後に
このツールを実際に使ってみて、当初の目的の略語検出よりも typo 検出の方が役に立ちました。
実際にヒドイ typo が見つかりました…
https://github.com/srz-zumix/iutest/commit/c063225f5abfeb157f671e3fc55b779f32e12193

ただ、typo チェックであれば、「Pull Requestに潜むタイポを自動的に検出し、修正を代行するBot - Qiita」の方がよさそうだなーと思ったのと、PyEnchant ってのがあるほを初めて知ったので機会があれば使ってみようと思いました。

最後ですが、今回のアップデートで自己満足できるところまで作れたので、これで開発を一旦終了しようと思います。
(要望いただければ対応します)
ではでは。

2017年7月24日月曜日

[CI] Bitrise で紹介プログラムが始まってた

Extra minutes on hobby plan! | Bitrise


以前の紹介したBitriseブログズミ: [CI] Bitrise 始めました)で、"Referral program(紹介プログラム)"が始まりました。
このプログラムは紹介するごとに、ビルド時間の上限が +5 分され、最大 +20 分できるものです。
条件や方法については、後述します。

無料プランではビルド時間 10分 の制限がある
Bitrise Pricing

Bitrise の Hobby plan (無料プラン)では、1ビルド10分の制限があります。

10分制限は、他の CI サービスと比べると結構短いです。
参考(少し古いですが…):「ブログズミ: 無料で使える CI サービス比較」

iutest でも、この制限回避のために iOS と Android のテストジョブを別個に用意してます。
(やりようによっては制限時間内に収められるかもしれませんし、分ける利点もあったりしますが…)

Referral program で無料プランでのビルド時間が最大 30 分になる
今回のプログラムを利用することで、ビルド時間が最大 30 分まで増やせるようになりました
1人紹介するごとに +5 分で、4人紹介で最大の 30 分になります。
さらに、紹介人数が5人になると Bitrise Tシャツがもらえるっぽいですw



条件と方法
まず、方法ですが、
Account Settings に Referrals のタブがあるのでそこを開きます。
(今なら上のバーにもリンクがありますね)
開くと、紹介用URL が右上に書いてありますので、この URL からアクセスしてもらうのが第一条件です。

筆者の場合 https://www.bitrise.io?referrer=a9c0b9baf7536067 です。(よろしくお願いしますm(__)m)

そして、アカウント登録をしてもらうこと、
さらに、ジョブ(App)を追加して、ビルドがグリーンであること、
これらの条件をすべて満たすと、紹介したことになり、+5 分されます。

筆者はまだ 0 人なので、公式ブログをキャプチャした画像になりますが、
こんな感じで状態が表示されるようです。



最後に
Bitriseは、iOS/Android のアプリ開発では定番の CI になっているようですし、それらの開発をしている方は是非使ってみてください。
また、最近どこの CI でも見るようになってきた ワークフロー も、Bitrise では使えますので、試してみたい方も是非!
そして、私に Bitrise T-shirt をください!




今回は以上です。では!