Visual Studio Community Edition でカバレッジ計測
概要
Visual Studioでカバレッジを計測できるかと思ったら、結構大変だったのでメモ。
環境
- OS
- Windows7 SP1 64bit
- IDE
- Microsoft Visual Studio Community 2015 ver14.0.23107
プロダクト作成
プロジェクト作成
今回はプロジェクト名をSampleとする。
プロダクトコード作成
今回はCartクラスを作成する。
using System.Collections.Generic; namespace Sample { public class Cart { public List<Item> Items { get; } public Cart() { Items = new List<Item>(); } public int Count { get { return Items.Count; } } public Item Get(int index) { return Items[index]; } public void Add(Item item) { Items.Add(item); } } }
ついでにカートの中に突っ込むItemクラスを作ってみる。
namespace Sample { public class Item { public int price { get; set; } public string name { get; set; } } }
テスト用プロジェクト作成
今回はテスト対象のテストがSampleなので、プロジェクト名を Sample.Test とする。
プロダクト参照をテストプロジェクトに設定
テストコード作成
今回はCartクラスに対してのみテストするため、名前をCartTestとする。なお、テストフレームワークは Visual Studio 標準。
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Sample; namespace SampleTest { [TestClass] public class CartTest { Cart target; [TestInitialize] public void カート作成() { target = new Cart(); } [TestMethod] public void 商品は空であること() { Assert.AreEqual(0, target.Items.Count); } [TestMethod] public void 商品が追加できること() { Item item = new Item(); target.Add(item); Assert.AreEqual(1, target.Count); Assert.AreSame(item, target.Items[0]); } } }
カバレッジ用のバッチを作成する
OpenCover を使ってコードカバレッジを計測したメモ - present
基本的に上記を参考に作成。しかし、私の環境では、OpenCoverやReportGeneratorが上記と異なるところにある。NuGetで取得したから?
以下のバッチファイルを、今回はテストプロジェクトのルートに置いておく。
また、今回自動でレポートを開くようにするため、以下を参考にした。
参考:バッチファイルからURLをブラウザで開く - Qiita
REM ###### Settings ###### SET PROJECT_NAME=Sample SET MS_TEST=C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\MSTest.exe SET REPORT_NAME=result.xml SET OUTPUT_DIR=.\html SET OPEN_COVER=.\packages\OpenCover.4.6.519\tools\OpenCover.Console.exe SET REPORT_GEN=.\packages\ReportGenerator.2.5.8\tools\ReportGenerator.exe SET TEST=.\%PROJECT_NAME%.Test\bin\Debug\%PROJECT_NAME%.Test.dll SET COVERAGE_DIR=.\%PROJECT_NAME%\bin\Debug\ SET FILTERS=+[%PROJECT_NAME%]* REM ####################### call :EXECUTE "%TEST%" start "" %OUTPUT_DIR%\index.htm exit :EXECUTE %OPEN_COVER% -register:user -target:"%MS_TEST%" -targetargs:"/noisolation /testcontainer:\"%~f1\"" -targetdir:%COVERAGE_DIR% -filter:"%FILTERS%" -output:%REPORT_NAME% -mergebyhash %REPORT_GEN% "%REPORT_NAME%" %OUTPUT_DIR% exit /b
設定項目は環境に合わせて変更する。
- PROJECT_NAME
- プロダクトのプロジェクト名。
- MS_TEST
- Microsoftの提供するテストアプリケーションの場所。バージョンやOSのビットによって変化するかも。
- REPORT_NAME
- デフォルトで特に動作に支障なし。レポート(xml)の出力先を指定する。
- OUTPUT_DIR
- デフォルトで特に動作に支障なし。レポート(htm)の出力先を指定する。
- OPEN_COVER
- OpenCoverのバージョンによって変わるかも。
- TEST
- テスト用dllのパス。デフォルト設定のDebugで生成されるパスを指定してある。異なる場所にあるなら変更。
- COVERAGE_DIR
- カバレッジを計るdllがあるディレクトリパス。デフォルト設定のDebugで生成されるパスを指定してある。異なる場所にあるなら変更。
- FILTERS
- カバレッジを計測するdllの条件を記載。
なお、Visual Studio で保存すると、なぜかBOM付きで保存されるので、テキストエディタでBOMを取らないと動かない。
上記は、UTF-8 BOMなし形式で動作確認している。
バッチ仕様
このバッチは、XXXがプロダクトのプロジェクト名だとすると、XXX.Testをテストプロジェクトとするように定めている。
参照dllはプロダクト・テストともDebugで出力されるものを使用する。
使い方
プロジェクトのビルド
最終的にdllを見ることになるので、両プロジェクトをビルドしておく。
パッケージマネージャー コンソールで以下のバッチコマンドを実行する
PS> .\Sample.Test\coverage.bat
以下のようなレポートが表示されれば成功。
NUnitの場合
NUnit.Console を NuGetから取得して、
バッチの2行を変更する。
# 4行目 SET MS_TEST=C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\MSTest.exe # 25行目 %OPEN_COVER% -register:user -target:"%MS_TEST%" -targetargs:"/noisolation /testcontainer:\"%~f1\"" -targetdir:%COVERAGE_DIR% -filter:"%FILTERS%" -output:%REPORT_NAME% -mergebyhash
↓
# 4行目 SET NUNIT=.\packages\NUnit.ConsoleRunner.3.6.1\tools\nunit3-console.exe # 25行目 %OPEN_COVER% -register:user -target:"%NUNIT%" -targetargs:"\"%~f1\"" -targetdir:%COVERAGE_DIR% -filter:"%FILTERS%" -output:%REPORT_NAME% -mergebyhash
これだと Visual Studio のインストール場所が依存しなくなるかも?
課題
事前にビルドしないとソースコードと異なる結果が出て勘違いするかもしれないので、ビルドしてから実行させる方がいい。
現状では、カバレッジに失敗してもエラー処理がないので、カバレッジ情報失敗しても最後まで処理が走ってしまう。
最後に
正直、色々なところで躓いた。まず、文献がそれほどないこと。
Visual Studio Ultimate 以上なら使えるらしいが、そうすると Professional に比べて値段がおよそ3倍以上になること。
OpenCoverをGUIで動作させることができるという拡張プラグイン(OpenCover UI)が動作しなかったこと。
バッチファイルの書き方がよく分からなかったり、Visual Studioでのバッチ起動がよく分からなかったり。
(パッケージマネージャーコンソールからの実行で本当にいいのか?^^;)
などなど。ただ、いい勉強にはなりました!
AWS CodeCommit 設定
CodeCommitとは?
AWS(Amazon Web Service) が提供するリモートリポジトリサービス。
ローカルにあるGitリポジトリをAWS側にアップロードすることにより、
複数ユーザによるソースコード管理が実現でき、まさかのPC障害によるデータ破損にも対応できる。
この他にも有名なサービスに、GitHub や BitBucketがある。
不特定多数の人間が開発に関わることを強みとしているのがGitHub。
小人数であればプライベートリポジトリを無料で利用(容量制限なし)できるのがBitBucketという印象がある。
なお、料金はアクティブユーザが5人以下であれば無料らしい。
料金 - Amazon CodeCommit | AWS
今回の設定内容
環境は Windows 7 Professional(SP1) 64bit。
ローカル環境にGitリポジトリを作成、CodeCommit 上のリモートリポジトリにアップロード。
Git Bash を使って、git push でリポジトリに変更が反映できるまでの確認を行う。
AWS CLI インストール
以下にアクセス。
AWS コマンドラインインターフェイス | AWS
ダウンロードしたMSIをインストール。デフォルト設定で問題なし。
インストール完了後は、Git Bashを開きインストール確認。
aws --version
以下のコマンドを叩いてバージョン情報が出ればインストール完了。
リモートリポジトリのアクセスユーザ設定
AWSにアクセス
クラウドならアマゾン ウェブ サービス【AWS 公式】
「セキュリティ認証情報」を選択
(ログイン画面が出たらログインすること)
「IAMユーザの使用開始」を選択
ユーザ作成
グループが存在しなかった場合は作成。
グループに対する権限設定は後で変更できる。今回は管理者権限を付与。
グループができるので、選択して「確認」へ。
ユーザ作成に成功したら、念のために認証情報をダウンロードしておく方が良いと思う。
なお、中身はアクセスキーIDとシークレットアクセスキーの情報が書いてある。
ローカルリポジトリにリモートリポジトリ紐づけ
以下を参考に実施。
Code Commitの使い方 - Qiita
AWS認証情報をローカルに登録。
profile=codecommit aws configure --profile $profile
必要な情報を入力する。
AWS認証情報登録の確認。
cat ~/.aws/credentials cat ~/.aws/config
今回は、ローカルリポジトリをsampleディレクトリに作成。
mkdir sample cd sample/ git init
リモートリポジトリアクセス時にAWS認証情報を使用するよう設定。
今回リージョンは「us-west-2」。そこは適宜変更する。
profile=codecommit region=us-west-2 # 現在のリポジトリのみ設定する場合 git config --local credential.helper '!aws codecommit --region '$region' --profile '$profile' credential-helper $@' git config --local credential.UseHttpPath true # 全てのgitリポジトリに設定する場合 git config --global credential.helper '!aws codecommit --region '$region' --profile '$profile' credential-helper $@' git config --global credential.UseHttpPath true
設定確認。
# 現在のリポジトリのみ設定する場合 cat ./.git/config # 全てのgitリポジトリに設定する場合 cat ~/.gitconfig
リモートリポジトリ宛先取得。
リモートリポジトリ登録。
url=https://git-codecommit.us-west-2.amazonaws.com/v1/repos/JUnitSample git remote add origin $url
リモートリポジトリ設定確認。
git remote -v
設定完了、挙動確認
ローカルの内容をリモートリポジトリにアップロード。
echo "AAA" > a.txt git add -A git commit -m "First Commit." git push origin master
CodeCommitにも a.txt が反映されればOK。
上手くいかなかった場合...
git push 時に403のエラーコードが出た場合は、以下の原因が考えられる。
- 認証設定が上手くいっていない
- 作成ユーザに適切な権限がなく実行できない
私は、git push 時、認証情報用のプロンプトが現れて、
正しいアクセス情報を入力しても403エラーになる事象が起こった。
調べてみると、GitのCredential Managerが有効になっていたためだった。
Troubleshooting AWS CodeCommit - AWS CodeCommit
※インストール時デフォルトでは有効になっている。
おまけ
シェルを作成してみた。
git リポジトリ上のパスで credential.csv および下記シェルを(aws_codecommit.sh)として実行。
引数に CodeCommit のリポジトリパスを渡して実行する。なお英語はかなり適当。
#!/bin/bash #----------------- # Set up AWS CodeCommit Authenticator. # 1. Put this shell and your credentials.csv into a directory which has .git directory. # 2. Execute this shell with repository url. #----------------- # usage: aws_codecommit.sh <url> [-p <profile>] [-r <region>] [--local | --global] # param <url>: repository url # param <region>: [default] Extract from <url> # param <profile>: [default] "codecommit" #----------------- URL="" REGION="" PROFILE="codecommit" GLOBAL=true CRED_CSV="./credentials.csv" URL_PATTERN="https:\/\/git-codecommit\.\([^.]\+\)\.amazonaws\.com\/.*" function set_param() { test -z `echo $1 | grep -e $URL_PATTERN` && return 1 URL=$1 REGION=`echo $1 | sed -e 's/^'$URL_PATTERN'$/\1/g'` shift while [ ! -z $1 ]; do case $1 in '-p') PROFILE=$2 shift;; '-r') REGION=$2 shift;; '--local') GLOBAL=false;; '--global') GLOBAL=true;; * ) return 1;; esac shift done return 0 } function show_err() { echo "[Error]: $1" } function show_usage() { cat $0 | grep -e "^# \(usage:\|param \)" | sed 's/^# \(.\+\)$/\1/g' } #------------------ # Main #------------------ set_param $@ if [ $? -ne 0 ]; then show_usage exit 1 fi if [ ! -e $CRED_CSV ]; then show_err "Not found credential file." exit 1 fi TMP_IFS=$IFS IFS="," set `sed -n 2P $CRED_CSV` KEY_ID="$3" SECRET_KEY="$4" IFS=$TMP_IFS if [ -z $KEY_ID -o -z $SECRET_KEY ]; then show_err "Invalid credential." exit 1 fi which aws > /dev/null if [ $? -ne 0 ]; then show_err "AWS CLI is not installed." exit 1 fi aws configure --profile $PROFILE > /dev/null << EOL $KEY_ID $SECRET_KEY $REGION EOL if [ $? -ne 0 ]; then show_err "Failed \"aws configure\"" exit 1 fi which git > /dev/null if [ $? -ne 0 ]; then show_err "Git is not installed." exit 1 fi GCONF_OPT="" case $GLOBAL in true) GCONF_OPT="--global";; false) GCONF_OPT="--local";; esac git config $GCONF_OPT credential.helper '!aws codecommit --region '$REGION' --profile '$PROFILE' credential-helper $@' && git config $GCONF_OPT credential.UseHttpPath true if [ $? -ne 0 ]; then show_err "Failed \"git config.\"." exit 1 fi git remote add origin $URL if [ $? -ne 0 ]; then show_err "Failed \"git remote add\"." exit 1 fi exit 0
FizzBuzz を20言語で頑張って書いてみる。
概要
標準出力・ループ・分岐などの練習のためFizzBuzz 問題を各言語で書いてみる。
知っている人は多いとは思うが、念のためFizzBuzzとは何かを説明する。
を言い合うゲームであり、これをプログラムで標準出力する。
簡単のため、入力はなしとし 1〜100(固定)までの数で行う。<実行例>
$ ./fizzbuzz 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz (16 〜 84を省略) Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz
言語基準
使用頻度の高そうなもの(参照:TIOBE Index | TIOBE - The Software Quality Company)のうち、自身が興味あるものを使用。
[除外言語]
アセンブラ→ 命令以外にもアセンブラ処理の疑似命令があったり、CPUによっても命令セットが異なるため除外
Delphi→ 開発環境が高い、無料版は使用可能日数が少ないため除外
COBOL→ サンプルソースコード見て吐きそう気軽に書けそうになかったため除外
注意点
できる限り処理内容が同じになるようにしています。
条件に対する各処理が同じ概念なら、if文はできる限り中括弧外してます。(そっちの方が読みやすいと思っているので)
末尾の改行のあるなしは結構適当だったり・・・。最大限でないようにはしてるつもりですが。
C/C++
#include<stdio.h> int main() { int i; for(i=1; i<=100; i++) { char tmp[9] = {}; if (i % 3 == 0) sprintf(tmp, "Fizz"); if (i % 5 == 0) sprintf(tmp, "%sBuzz", tmp); if (!*tmp) sprintf(tmp, "%d", i); puts(tmp); } return 0; }
C#
LINQを使った実装。
using System; using System.Linq; namespace ConsoleApplication { class FizzBuzzMain { static void Main(string[] args) { Console.WriteLine(string.Join("\n", Enumerable.Range(1, 100).Select(FizzBuzz))); } static string FizzBuzz(int i) { string tmp = ""; if (i % 3 == 0) tmp = "Fizz"; if (i % 5 == 0) tmp += "Buzz"; return tmp.Length == 0 ? i.ToString() : tmp; } } }
D
import std.stdio; import std.conv; string fizzbuzz(int i) { auto tmp = ""; if (i % 3 == 0) tmp = "Fizz"; if (i % 5 == 0) tmp ~= "Buzz"; if (tmp.length == 0) tmp = to!string(i); return tmp; } void main() { foreach(i; 1..101) writeln(fizzbuzz(i)); }
Objective-C
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableArray *list = [NSMutableArray array]; for (int i=1; i<=100; i++) { NSMutableString *tmp = [NSMutableString string]; if (i % 3 == 0) [tmp appendString:@"Fizz"]; if (i % 5 == 0) [tmp appendString:@"Buzz"]; [list addObject:([tmp length] ? tmp : [NSString stringWithFormat:@"%d", i])]; } printf("%s", [[list componentsJoinedByString:@"\n"] UTF8String]); } return 0; }
Swift
import Foundation func fizzbuzz(num: Int) -> String { if (num % 15 == 0) { return "FizzBuzz" }else if (num % 3 == 0) { return "Fizz" }else if (num % 5 == 0){ return "Buzz" }else { return num.description } } print(join("\n", (1 ... 100).map(fizzbuzz)))
Java
import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 1; i <= 100; i++) { StringBuilder sb = new StringBuilder(); if (i % 3 == 0) sb.append("Fizz"); if (i % 5 == 0) sb.append("Buzz"); list.add(sb.length() == 0 ? String.valueOf(i) : sb.toString()); } System.out.print(list.stream().collect(Collectors.joining("\n"))); } }
Groovy
Rubyの影響を受けた言語らしくかなり似てる。関数言語で頻繁に使う map がcollectっていう名前だった。
def fizzbuzz(int num) { def tmp = "" if (num % 3 == 0) tmp = "Fizz" if (num % 5 == 0) tmp += "Buzz" return tmp.length() == 0 ? num.toString() : tmp } print((1..100).collect (this.&fizzbuzz).join("\n"))
Bash
IFSに改行セットするのに苦労。
#!/bin/bash list=() for i in {1..100}; do TMP="" if [ $(($i % 3)) -eq 0 ]; then TMP="Fizz" fi if [ $(($i % 5)) -eq 0 ]; then TMP=$TMP"Buzz" fi list[i-1]=${TMP:-$i} done IFS=$'\n' echo "${list[*]}"
Python
#!/usr/bin/python def fizzbuzz(num): tmp = "" if num % 3 == 0: tmp = "Fizz" if num % 5 == 0: tmp += "Buzz" return tmp if len(tmp) != 0 else str(num) list = [] for i in range(1, 101): list.append(fizzbuzz(i)) print("\n".join(list))
PHP
<?php $list = array(); for ($i=1; $i<=100; $i++) { $tmp = ""; if ($i % 3 == 0) { $tmp = "Fizz"; } if ($i % 5 == 0) { $tmp = $tmp . "Buzz"; } $list[] = empty($tmp) ? $i : $tmp; } echo implode("\n", $list);
Perl
#!/usr/bin/perl @list = (); foreach $i (1..100) { $tmp = ""; if ($i % 3 == 0) { $tmp .= "Fizz"; } if ($i % 5 == 0) { $tmp .= "Buzz"; } push(@list, length($tmp) ? $tmp : $i); } print(join("\n", @list));
Ruby
print (1..100).map { |i| tmp = "" tmp << "Fizz" if i % 3 == 0 tmp << "Buzz" if i % 5 == 0 tmp.empty? ? i.to_s : tmp }.join("\n")
JavaScript
var list = [] for(var i=1; i<=100; i++) { var tmp = ""; if (i % 3 == 0) tmp = "Fizz"; if (i % 5 == 0) tmp += "Buzz"; list.push(tmp.length == 0 ? i.toString() : tmp); } console.log(list.join("\n"));
Go
なんか色々用語が特殊だったり(なんだよSliceって!)、普通ある構文がなかったり(?:演算子)とちょっと苦労した。
package main import ( "fmt" "unicode/utf8" "strings" ) func main() { result := []string{} for i := 1; i <= 100; i++ { tmp := "" if i % 3 == 0 { tmp = "Fizz" } if i % 5 == 0 { tmp += "Buzz" } if utf8.RuneCountInString(tmp) == 0 { tmp = fmt.Sprintf("%d", i) } result = append(result, tmp) } fmt.Print(strings.Join(result, "\n")) }
Scala
joinがないと思ったらmkStringという珍しいメソッド名だった。
object Main { def fizzbuzz(n: Int): String = { if (n % 15 == 0) "FizzBuzz" else if (n % 3 == 0) "Fizz" else if (n % 5 == 0) "Buzz" else n.toString() } def main(args: Array[String]): Unit = { print((1 to 100).map(fizzbuzz).mkString("\n")) } }
Clojure
最初みたときは気持ち悪いと思ったけど、使ってみると意外と悪くない。
(ns fizzbuzz.core (:gen-class)) (defn- factor? [factor, n] (if (= (mod n factor) 0) true false)) (defn fizzbuzz [n] (cond (factor? 15 n) "FizzBuzz" (factor? 3 n) "Fizz" (factor? 5 n) "Buzz" :else (str n))) (defn -main [& args] (print (clojure.string/join "\n" (map fizzbuzz (range 1 101)))))
Haskell
import Data.List fizzbuzz :: (Show a, Integral a) => a -> String fizzbuzz x | isFactorOf 15 = "FizzBuzz" | isFactorOf 3 = "Fizz" | isFactorOf 5 = "Buzz" | otherwise = show(x) where isFactorOf y = x `mod` y == 0 main :: IO() main = putStrLn $ intercalate "\n" $ map fizzbuzz (take 100 [1..])
F#
let fizzbuzz num = let f n = num % n = 0 if f 15 then "FizzBuzz" else if f 3 then "Fizz" else if f 5 then "Buzz" else num.ToString() [<EntryPoint>] let main argv = printfn "%s" (String.concat "\n" (List.map fizzbuzz [1..100])) 0
VisualBasic/VB.NET
Module FizzBuzz Sub Main() Console.WriteLine(String.Join(vbCrLf, Enumerable.Range(1, 100).Select(AddressOf FizzBuzz))) End Sub Function FizzBuzz(i As Integer) Dim tmp = "" If i Mod 3 = 0 Then tmp += "Fizz" End If If i Mod 5 = 0 Then tmp += "Buzz" End If Return IIf(tmp.Length = 0, i.ToString(), tmp) End Function End Module
R
fizzbuzz <- function (n) { tmp <- "" if (n %% 3 == 0) tmp <- "Fizz" if (n %% 5 == 0) tmp <- paste0(tmp, "Buzz") if (nchar(tmp) == 0) tmp <- as.character(n) tmp } for(i in 1:100) print(fizzbuzz(i))
おわりに
各言語の特徴的な機能をなるべく用いて実装したつもりですが、経験のない言語が多く
この程度の内容でも全部書くのに4日かかりました。(今回のために改めて開発環境作ったものも多い)
やってみた感想として。
15言語目くらいで心が折れかけました。面白い処理書いているならいいんですが、
FizzBuzzのために、違う言語とはいえ、ひたすら同じ項目をネットで調べるのは精神衛生上あまり良くなかったです。
・・とはいえ、同じような作業やってて、いくつか気づきもありました。
近年流行の言語は関数型言語の影響を受けていて、その共通のメソッドがすでにライブラリとして実装されているため、
関数言語を一つやっていれば、簡単な処理くらいならすぐ書けそうってこと。
また、ある言語を使うときに影響を受けた言語を知っていると、どんなものが記述できそうか想像できるため学習コストが間違いなく減ります。
事実、Swift, F#, Groovy などは今回初コーディングですが、既に知っている言語に記述が似ていた事、IDEの補完機能のおかげもあり時間がほぼ掛かっていません。
こういう意味では、広く浅く言語の特徴を知っている事は今後のための投資ととらえれば悪い事ではないと思います。
今回は簡単なものとして FizzBuzz で記述したけど、記述方法の違い程度しか分からないものも多いので、
今後機会があれば、別バージョンやろうと思います。言語数は減らすと思いますが!
Clojure 開発環境を cygwin 上に作る
構築で参考にした記事
Windows でも Clojure がしたい! - Qiita
- JDKインストール
- windows用インストーラダウンロード (leiningen-win-installer)
- インストール
シェルをダウンロード
GitHub - technomancy/leiningen: Automate Clojure projects without setting your hair on fire.
ここにあるシェル(lein script)をダウンロードする。
もし、上記で場所分からなかったら以下をダウンロード。(2016/09/23現在版。古くなっているかも。)
https://raw.githubusercontent.com/technomancy/leiningen/preview/bin/lein
(インストール先)\.lein\bin の中に「lein」って名前で保存。
cygwin 上で起動できない
ここまでやれば起動できるはず!
・・ってことで、下記コマンドを実行してみるが動かない。
$ lein /cygdrive/p/.lein/bin/lein: 行 364: P:\Java\jdk1.8.0_60\bin\java.exe: コマンドが見つかりません
見つかってないコマンドのパスは、シェルの364行目をみると環境変数「LEIN_JAVA_CMD」で指定されたものらしい。
Javaファイルを認識できていない可能性があるので、
$ java -version java version "1.8.0_91" Java(TM) SE Runtime Environment (build 1.8.0_91-b15) Java HotSpot(TM) 64-Bit Server VM (build 25.91-b15, mixed mode)
・・と、すると lein が java のコマンドを認識できていないってことか。
探してみると、Stack Overflow のページに「普通に自分は動いたよ!」っていう方がいて、
その設定を載せていたので、自分の環境に合わせてコピってみる。
clojure - Getting Leiningen & Cygwin Working - Stack Overflow
JAVA_HOME="/cygdrive/c/Program Files/Java/jdk1.8.0_05/" LEIN_JAVA_CMD="${JAVA_HOME}/bin/java" JAVA_CMD=`cygpath -w "${LEIN_JAVA_CMD}"`
上記を適用すると動いた。どうやら cygwin 流のパスを lein が認識できていないようだ。
.bashrc に追加
・・ってことで、~/.bashrc にラッパースクリプト書いて、起動時にのみ動くようにした。
(各自環境と照らし合わせてjavaのバージョン・パスを変更してください)
LEIN_PATH=`which lein` function lein() { local JAVA_HOME="/cygdrive/c/Program Files/Java/jdk1.8.0_60/" local LEIN_JAVA_CMD="${JAVA_HOME}/bin/java" local JAVA_CMD=`cygpath -w "${LEIN_JAVA_CMD}"` "$LEIN_PATH" $@ }
Haskell を cygwin 上で動かすのに苦労した話
概要
Haskell の動作環境を用意する。cygwin上で動かそうとすると少し苦労したのでメモ。
インストーラのダウンロード
以下をURLから、Windows版のHaskell Platform のインストーラのうち環境に適したものをダウンロード。
Download Haskell Platform
なお、私がダウンロードしたのは、Full(64bit)版。
インストール
指示に従っていけば特に問題なし。パスもインストーラが作成してくれる。
Cygwin で ghci 起動
cygwin を起動して、ghci(対話モードのインタプリタ)を起動してみると・・。
$ ghci WARNING: GHCi invoked via 'ghci.exe' in *nix-like shells (cygwin-bash, in particular) doesn't handle Ctrl-C well; use the 'ghcii.sh' shell wrapper instead GHCi, version 8.0.1: http://www.haskell.org/ghc/ :? for help Prelude>
こんなメッセージが表示される。
警告に書いてある通り、Ctrl + C を用いると、プロンプトは出てくれるのだがそこから何も入力できなくなる。
関連プロセス(ghc.exe)はなぜか生存中。このプロセスをタスクマネージャから強制終了すると入力ができるようになる。
これを防ぐために、ghciコマンドで起動している「ghci.exe」ではなく、「ghcii.sh」が起動するように変更する。
このファイルは「(プログラムフォルダ)\Haskell Platform\8.0.1\bin」に存在ある。
「ghcii.sh」→「ghci」にリネームする。そうすると、「ghci.exe」より優先度が高くなり、シェルの方を呼び出してくれる。
変更すると以下のようになる。
$ ghci GHCi, version 8.0.1: http://www.haskell.org/ghc/ :? for help Prelude>
ghc でコンパイルできなかった
Haskell のソースファイルをghcを使ってバイナリにコンパイルするときに、私の環境では失敗した。
$ ghc fizzbuzz.hs -o fizzbuzz Linking fizzbuzz.exe ... fd:5: hGetContents: invalid argument (invalid byte sequence) ghc.exe: fd:5: hGetContents: invalid argument (invalid byte sequence)
もう一回起動してみると、なぜかメッセージが変わった。
$ ghc fizzbuzz.hs -o fizzbuzz Linking fizzbuzz.exe ... realgcc.exe: error: s\Java\▒▒▒K: No such file or directory P:\Program Files\Haskell Platform\8.0.1\lib/../mingw/bin/windres.exe: "P:\Program Files\Haskell Platform\8.0.1\lib/../mingw/bin/gcc.exe ▒̓X▒e▒[▒^▒X 1 ▒ŏI▒▒▒▒▒܂▒▒▒ `windres.exe' failed in phase `Windres'. (Exit code: 1)
今度は文字化けが発生。
エラーメッセージを調べていたら、
「LANG=C.utf-8 stack build works as intended.」(stack build は環境変数 LANGがC.utf-8だったら意図したとおりに動いてるよ)
とかあったので、文字化けも考慮して、LANGを確認。
$ echo $LANG ja_JP.UTF-8
C.UTF-8に変更。
$ export LANG='C.UTF-8' $ echo $LANG C.UTF-8 $ ghc fizzbuzz.hs -o fizzbuzz Linking fizzbuzz.exe ... $ ./fizzbuzz 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz (省略)
動いた!...でも。これが本当に原因か分からないので、動作検証。元に戻してみる。
$ export LANG='ja_JP.UTF-8' $ echo $LANG ja_JP.UTF-8 $ touch fizzbuzz.hs $ ghc fizzbuzz.hs -o fizzbuzz [1 of 1] Compiling Main ( fizzbuzz.hs, fizzbuzz.o ) Linking fizzbuzz.exe ... realgcc.exe: error: icrosoftw▒E ▒▒: Invalid argument P:\Program Files\Haskell Platform\8.0.1\lib/../mingw/bin/windres.exe: "P:\Program Files\Haskell Platform\8.0.1\lib/../mingw/bin/gcc.exe ▒̓X▒e▒[▒^▒X 1 ▒ŏI▒▒▒▒▒܂▒▒▒ `windres.exe' failed in phase `Windres'. (Exit code: 1)
・・・ってことで、これが原因らしい。
なお、touchコマンド使っているのは、ghc は内部でソースファイル、オブジェクトファイルの更新時間を見てるようで、
タイムスタンプ更新しないとコンパイルしてくれないため。
RaspberryPi に Rest API を簡単に実装
概要
Raspberry Pi に対して情報を取得する際に HTTPを利用してこれを行いたい場合がある。
ruby のウェブフレームワークで有名なのは、Rails だが、Rails は大規模向けであるため
簡素な実装をするにも、構築時間、学習コストが見合わないことがある。
今回は ruby 初心者でも手軽に実装できる、軽量フレームワークのSinatraを導入し、Rest APIを実装してみた。
Sinatra 導入
ruby, gem 導入
このあたりで多分導入できる(はず)。
Raspberry piにRubyの最新版をインストールする - Qiita
なお、自身のRaspberryPiにはすでにインストールされていたため、実施していません。
バージョン関連の情報は以下。
$ ruby -v ruby 2.1.5p273 (2014-11-13) [arm-linux-gnueabihf] $ gem -v 2.2.2
URI、コンテンツ定義
以下のファイルをどこでもいいので、作成する。(下記を app.rb として保存する)
require 'sinatra' set :bind, '0.0.0.0' # これを書くと http://localhost:4567 以外からでもアクセス可能。 get '/' do # ルートに GET でアクセスした場合 "Top!\n" # 返却する内容 end get '/:name' do |n| # /{name} GET でアクセスした場合 "Hello #{n}\n" # {name}の部分を値として使う end
サーバ起動
$ ruby app.rb
アクセス
※ 192.168.0.30 はRaspberrypiのプライベートアドレス
http://192.168.0.30:4567/
http://192.168.0.30:4567/aaa
実装例
これだけであれば、インストール除けば5分で作成可能。
メモリとディスクのプロパティを入手する API を作ってみた。
require 'sinatra' require 'json' set :bind, '0.0.0.0' disk = Struct.new("Disk", :name, :path, :total, :used) get '/disk' do disks = `df -l | sed -e "1d"`.split("\n").inject([]) do |list, line| arr = line.split("\s") list << disk.new(arr[0], arr[5], arr[3].to_i, arr[2].to_i).to_h end JSON.generate({"disks" => disks}) end memory = Struct.new("Memory", :total, :used, :free, :shared, :buffers, :cached) get '/memory' do arr = `free | sed -n -e "2p"`.split("\s") mem = memory.new(arr[1].to_i, arr[2].to_i, arr[3].to_i, arr[4].to_i, arr[5].to_i, arr[6].to_i) JSON.generate(mem.to_h) end
Cygwin 上で curl 実行できれば、下記コマンドでいける。
$ curl http://192.168.0.30:4567/disk | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 580 100 580 0 0 3505 0 --:--:-- --:--:-- --:--:-- 3602 { "disks": [ { "name": "/dev/root", "path": "/", "total": 22113944, "used": 6837300 }, { "name": "/dev/sda1", "path": "/media/hdd1", "total": 455974524, "used": 177392 }, ] } $ curl http://192.168.0.30:4567/memory | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 91 100 91 0 0 1925 0 --:--:-- --:--:-- --:--:-- 2166 { "total": 948012, "used": 879992, "free": 68020, "shared": 28708, "buffers": 206668, "cached": 267452 }
まとめ
テンプレート利用やルーティング条件などが設定できるため、ある程度難しいこともできそうだ。
もし、バックエンドでのみ使用する API であれば、製造コスト的に選択の一つとしてはいいかもしれない。
シェルスクリプト分岐・ループ構文例
概要
Linux 上で便利な機能やマクロ的に処理を行うためにシェルスクリプトを記述することがある。
その中でも比較的よく見かける分岐・ループ構文をメモ。
ファイル状態によって操作を変える
テストコマンドでファイルの状態を確認できる。他にも様々なことができる。詳しくは下記参照。
Linuxコマンド集 - 【 test 】 条件式の真偽を判定する:ITpro
if [ -e 'aaa.txt' ]; then # ファイル aaa.txt が存在するかどうか echo 'exist' # 存在するならexist出力 fi
条件満たす場合・満たさない場合の処理実行
if [ -e 'aaa.txt' ]; then # ファイル aaa.txt が存在かどうか echo 'exist' # 存在するならexist出力 else echo 'not found' # 存在しないならnot found出力 fi
複数条件および処理実行
if [ -s 'aaa.txt' ]; then # ファイル aaa.txt が存在して空ファイルでない場合 echo 'size not 0' # 存在して空ファイルでないならsize not 0出力 elsif [ -e 'aaa.txt' ]; # ファイルが存在するかどうか echo 'empty' # 空ファイルが存在するならempty出力 else echo 'not found' # ファイルが存在しないならnot found出力 fi
exitコードを利用した処理分岐
bash ではexitコードが0なら真、そうでなければ偽となる。これらを利用して &&, || を利用して分岐を書ける。
- && は直前のステータスが 0 なら次を実行
- || は直前のステータスが 0 以外なら次を実行
なお、「{」「}」はコマンドのグループ化であり、各文字前後にはスペース必要。「}」の直前コマンドにはセミコロンが必要。
{ [ -s 'aaa.txt' ] && echo 'size not 0'; } || { [ -e 'aaa.txt' ] && echo 'exist'; } || echo 'not found'
上記を branch.sh として作成してテストすると下記のように出力内容が変化する。
$ rm aaa.txt # ファイルが存在しないようにする rm: 'aaa.txt' を削除できません: No such file or directory $ . branch.sh not found $ touch aaa.txt # サイズ 0 のファイル作成 $ . branch.sh exist $ echo "aaa" >> aaa.txt # ファイルに aaa を追加してサイズを増やす $ . branch.sh size not 0
変数の中身に応じて操作を変更する
NUM=100 case $NUM in # NUMの値が 100) echo 'ok';; # 100 なら 0) echo 'not work';; # 0 なら *) echo '???';; # それ以外 esac
ある範囲の数値に対する操作
SUM=0 for NUM in `seq 1 100`; do # 1-100までに対して操作 SUM=`expr $SUM + $NUM` # 加算 done echo $SUM # 結果出力
bashならば以下の記法が使える。
SUM=0 for ((i=1; i<=100; i++)) do SUM=`expr $SUM + $i` done echo $SUM
ある条件を満たすまで操作する
合計が5000超えるまで計算。
SUM=0 NUM=0 while [ $SUM -lt 5000 ] && NUM=`expr $NUM + 1`; do SUM=`expr $SUM + $NUM` done echo $SUM # 合計値
引数に END が出るまで引数を出力する
while [ -n "$1" ] && [ "$1" != "END" ]; do # 現在処理中の引数が空文字でも END でもないなら処理を行う echo "$1" # 位置パラメータ1番目出力 shift # 位置パラメータをシフト done
シェル引数に対する処理
# シェル引数は何もしなければ位置パラメータに入っている for ARG in "$@"; do # 各位置パラメータに対して操作 echo "$ARG" # 出力 done
標準入力から各要素を取り出し操作
区切り文字としてカンマ(,)が使われた場合の例
read INPUT # このコマンドで入力待ちになる IFS=',' # 区切り文字の指定 set -- $INPUT # 位置パラメータに入力内容をセット for ELEMENT in "$@"; do # それぞれに対して操作 echo "$ELEMENT" done
特定ファイル群に対する操作
for FILE in *.txt; do # カレントディレクトリのテキストファイルに対して操作 echo "$FILE" # ファイル名出力 done
直前のコマンド処理結果を操作
cat 'aaa.txt' | grep "AAA" | while read LINE; do # aaa.txt 内にある AAA が存在する行に対して操作 echo "$LINE" # 内容出力 done
ファイル各行に対する処理
IFS=',' # 区切り文字指定。今回はCSVを処理するためカンマ指定 while read LINE; do # 一行読み込み set -- $LINE # 位置パラメータに一行情報セット echo "(1)$1, (2)$2, (3)$3" # 各列情報出力 done < 'test.txt' # 読み込むファイルはリダイレクトで受け取る