Status Code 303 - See Other

Java, C, C++, C#, Objective-C, Swift, bash, perl, ruby, PHP, Python, Scala, Groovy, Go, DevOps, Raspberry Pi など。情報の誤りや指摘・意見などは自由にどうぞ。

C#のAOPライブラリ

AOP(Aspect Oriented Programming)?

ログ出力や例外処理など、メソッド全体に共有な処理を重複定義せず一か所に定義したいことがある。
このような共有処理を側面(Aspect)として定義した後、メソッドに適用する手法。

もし、全部それぞれにコピペなどで定義しまった場合。
その部分に修正が入った場合、全てのメソッドを直さないといけなくなり、保守性が低下する。
さらに、本来クラスにさせたい処理でないため、コードの見通しや可読性が低下する。

PostSharp

C#では有名なAOPライブラリ。本来は有料だが、Expressエディションを使えば適用数が制限されるが、無料で実行できる。
(プロジェクト単位:最大10個、ソリューション:最大50個)

なお、最上位版のPostSharp Ultimateを購入すると大体10万円かかるらしい。
PostSharp – the #1 pattern-aware extension to C# and VB

どんなとき使うの?

ドキュメントに記載されている、よくある使用法。

  • プロパティ値が変更されたときの通知
  • UndoとRedo
  • 契約プログラミング(事前条件/事後条件確認)
  • ログ・監視・プロファイリング
  • 例外発生時の制御
  • トランザクション制御
  • マルチスレッド時の挙動制御(バックグランド・フォアグランド制御、不変性・同期など)
  • セキュリティ(入力チェック、認証など)

使い方

サンプルクラス

今回は以下のSampleクラスのメソッドに対して、メソッド開始・終了にログを出力できるようにする。

using System;

namespace PostSharpSample
{
	class Sample
	{
		public void Execute()
		{
			Console.WriteLine("Hello {0}!", GetVal());
		}

		public string GetVal()
		{
			return "PostSharp";
		}
	}
}
PostSharpインストー

NuGetからPostSharpをインストールする。なお、ビルド時に設定ウィンドウが現れるので、Expressを使うと。

AOP属性定義

今回は、メソッド起動時および終了時に起動ログを標準出力する属性を定義する。

using PostSharp.Aspects;
using PostSharp.Serialization;
using System;

namespace PostSharpSample
{
	[PSerializable]
	class LoggerAttribute : OnMethodBoundaryAspect
	{

		public override void OnEntry(MethodExecutionArgs args)
		{
			Console.WriteLine("OnEntry {0}", args.Method.Name);
		}

		public override void OnExit(MethodExecutionArgs args)
		{
			Console.WriteLine("OnExit {0}", args.Method.Name);
		}
	}
}
適用メソッドに属性付加
using System;

namespace PostSharpSample
{
	class Sample
	{
		[Logger]
		public void Execute()
		{
			Console.WriteLine("Hello {0}!", GetVal());
		}

		[Logger]
		public string GetVal()
		{
			return "PostSharp";
		}
	}
}
起動

コンソールアプリで起動する。

using System;

namespace PostSharpSample
{
	class Program
	{
		static void Main(string[] args)
		{
			Console.WriteLine("Start Main");

			new Sample().Execute();

			Console.WriteLine("End Main");
			Console.Read();
		}
	}
}

すると以下のような出力になる。

Start Main
OnEntry Execute
OnEntry GetVal
OnExit GetVal
Hello PostSharp!
OnExit Execute
End Main

参考までに

ドキュメントはこちら。PostSharp Documentation
今回の属性適用によって、ビルド時に適用されたメソッドは以下のようになるらしい。
参照:OnMethodBoundaryAspect Class

int MyMethod(object arg0, int arg1)
{
   OnEntry();
   try
   {
    // Original method body. 
    OnSuccess();
    return returnValue;
  }
  catch ( Exception e )
  {
    OnException();
  }
  finally
  {
    OnExit();
  }
}

この他にも、無料のAOPツールにFodyってのがあるらしいが、上手く動かなかったため後日再チャレンジ予定。

JUnitテスト前提条件制御

概要

毎回テストをするうえでお決まりの処理というのがある。
これらの処理をまとめる機構がテストフレームワークには大体存在する。
今回はJUnitの便利なテスト前提条件を設定する方法について記述する。

これらをやることによって、以下の利点がある。

  • テストがどんなものが分かりやすくなる
  • 処理が一か所にまとまる
  • テストの本質的な内容だけが目立つようになる

詳細説明

今回は、seleniumでサイトテストを作成しようとした例で説明する。
seleniumはブラウザを用いた機能ごとのテストや結合・シナリオテストで使われるライブラリだ。
なぜ、seleniumを用いたテストかというと、毎回初期化しないとあるテストが他のテストに影響しやすいからだ。

散在したテスト前提条件をまとめる

テストを実行するとき、前提条件・事後条件を定義したうえでのテストが多い。
一例としては、下記の内容だ。

  • 現在あるページにいること
  • ログインしていること

これらは、通常であれば JUnit が提供している @Before/@After を利用すれば事足りるが、
これが多数のテストクラスに必要だった場合、同じロジックが多数クラスに散在してしまう。

この問題を解決するために、@Rule というものがある。これは、@Before/@Afterをまとめる役割をする。
この@Ruleがつけられたフィールドをクラス内に配置すると、JUnitがテストの実行前後にロジックを実行してくれる。

public class LoginTest {

	/**
	 * テストルール.
	 */
	@Rule
	public WebTestRule rule = new WebTestRule();

	/**
	 * ブラウザ.
	 */
	Browser browser;

	@Before
	public void setUp() {
		browser = rule.getBrowser();
		browser.navigateTo(loginPage.getUrl());
	}

	@Test
	public void ログインテスト() throws Exception {
		User user = new User("ID", "PW");
		browser.getElement(loginPage.idText).sendKeys(user.getId());
		browser.getElement(loginPage.passwordText).sendKeys(user.getPassword());
		browser.getElement(loginPage.submitButton).click();

		assertThat(browser.getUrl(), is(new MemberPage().getUrl()));
	}

	@Test
	public void ログインが失敗すること() throws Exception {
		User user = new User("IDErr", "PWErr");
		browser.getElement(loginPage.idText).sendKeys(user.getId());
		browser.getElement(loginPage.passwordText).sendKeys(user.getPassword());
		browser.getElement(loginPage.submitButton).click();

		assertThat(browser.getUrl(), is(loginPage.getUrl()));
		assertThat(browser.getElement(loginPage.errorComment).getText(), is("認証情報が違います"));
	}
}

@Ruleがつけられたインスタンスの実装内容は以下のようになる。
ここでは、ブラウザをテスト前に開いて、テスト後に閉じている。

public class WebTestRule extends ExternalResource {

	protected Browser browser = new Browser();

	public Browser getBrowser() {
		return browser;
	}

	@Override
	protected void before() throws Throwable {
		browser.open();
	}

	@Override
	protected void after() {
		browser.close();
	}
}

最終的にこのテストでは、下記の順番でメソッドが動くことになる。

  1. @Ruleが付与されたフィールドのbeforeメソッド
  2. @Before が付与されたメソッド
  3. テスト実行
  4. @Ruleが付与されたフィールドのafter

@Ruleの内容によって、これがWebのテストであると分かるし、
その詳細なロジックはこのクラス内には記載されていないので、テスト内容だけが目立つようになる。

テスト実行前・実行後の挙動を追加

また、挙動の追加もそれほど難しくない。
例えば、selenium起動前後にさらにロジックを追加したい場合もあるだろう。
この場合、以下の2ステップで変更できる。

  1. 既存のRuleクラスを拡張したクラスを作成
  2. @Ruleを付与したフィールドを拡張したクラスインスタンスに置き換えるだけ。

例えば、seleniumでブラウザを起動した後、あるサイトにログインした状態を作ってから、テストをしたい場合があるとする。
その場合、WebTestRuleクラスを継承した以下のクラスを作成すればよい。

public class LoginRule extends WebTestRule {

	@Override
	protected void before() throws Throwable {
		super.before();
		LoginPage login = new LoginPage();
		super.browser.navigateTo(login.getUrl());
		super.browser.getElement(login.idText).sendKeys("ID");
		super.browser.getElement(login.passwordText).sendKeys("PW");
		super.browser.getElement(login.submitButton).click();
	}
}
テストクラス実行前と実行後のみ動かしたい場合

次に、beforeメソッド・afterメソッドの実行タイミング変更について記述する。
これまでのルールは、各テストごとにbeforeメソッド・afterメソッドが起動するため、
テストが多くなるとテスト時間が多くかかってしまう。(seleniumドライバの初期化処理が軽くないため)
これを軽減するためによく使われる方法として、全てのテストに共通のまたがった環境をつくる。

これは @Rule → @ClassRule に変更すれば可能であり、下記の動作になる。

  • 最初のテストの実行前 → beforeメソッドが起動
  • 最後のテストの実行後 → afterメソッドが起動
public class ログイン前状態 {

	/**
	 * テストルール.
	 */
	@ClassRule
	public WebTestRule rule = new WebTestRule();

(以下省略)

このテストは速度が向上する代わりに、あるテストが他のテストに影響してしまう危険性もある。
例えば、ログインした状態とそうでない状態で結果が全く異なる場合。

この場合、よく用いられるのは、ログイン処理→検証→ログアウト処理をテストメソッドに直接記述する方法。
なぜなら、それを共通の箇所(@Before/@Afterが付与されたメソッド)に書くと毎回起動されてしまうから。

しかし、これはテストとして間違っていると思う。本来テストには、テスト処理と検証だけがあるべきだ。
仮に末尾のログアウト処理で例外が発生した場合、それはこのテストの失敗であるといえるだろうか?

複数条件をテストクラスに記述する

この問題を解決するには、Enclosedテストランナーを使う。
これらは、別々の前提条件をテストクラスに複数含めることができる。

@RunWith(Enclosed.class)
public class LoginPageTest {

	/**
	 * テスト対象.
	 */
	static LoginPage loginPage = new LoginPage();

	public static class ログイン状態 {

		@Rule
		public LoginRule rule = new LoginRule();

		@Test
		public void メンバーページに遷移すること() throws Exception {
			rule.getBrowser().navigateTo(loginPage.getUrl());
			assertThat(browser.getUrl(), is(new MemberPage().getUrl()));

		}
	}

	public static class ログイン前状態 {

		@Rule
		public WebTestRule rule = new WebTestRule();

		Browser browser;

		@Before
		public void setUp() {
			browser = rule.getBrowser();
			browser.navigateTo(loginPage.getUrl());
		}

		@Test
		public void ログインテスト() throws Exception {
			User user = new User("ID", "PW");
			browser.getElement(loginPage.idText).sendKeys(user.getId());
			browser.getElement(loginPage.passwordText).sendKeys(user.getPassword());
			browser.getElement(loginPage.submitButton).click();

			assertThat(browser.getUrl(), is(new MemberPage().getUrl()));
		}

		@Test
		public void ログインが失敗すること() throws Exception {
			User user = new User("IDErr", "PWErr");
			browser.getElement(loginPage.idText).sendKeys(user.getId());
			browser.getElement(loginPage.passwordText).sendKeys(user.getPassword());
			browser.getElement(loginPage.submitButton).click();

			assertThat(browser.getUrl(), is(loginPage.getUrl()));
			assertThat(browser.getElement(loginPage.errorComment).getText(), is("登録されていませんよ?"));
		}
	}
}

最後に

今回は、テスト項目の制御という目的で @Ruleと@ClassRuleについて触れた。
これらがJUnitですっきりしたテストコードを書く上では大事なものであることはまちがいない。
テストがきれいであれば色んな人にも読みやすいし、これからテストを書く人にも敷居が低く感じてもらえるはずだ。

Visual Studio Community Edition でカバレッジ計測

概要

Visual Studioカバレッジを計測できるかと思ったら、結構大変だったのでメモ。

環境

OS
Windows7 SP1 64bit
IDE
Microsoft Visual Studio Community 2015 ver14.0.23107

プロダクト作成

プロジェクト作成

今回はプロジェクト名をSampleとする。
f:id:kouki_hoshi:20170520051925p:plain
f:id:kouki_hoshi:20170520051931p:plain

プロダクトコード作成

今回は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; }
	}
}

f:id:kouki_hoshi:20170520052007p:plain

テスト用プロジェクト作成

今回はテスト対象のテストがSampleなので、プロジェクト名を Sample.Test とする。
f:id:kouki_hoshi:20170520052017p:plain
f:id:kouki_hoshi:20170520052024p:plain

テスト用プロジェクトにカバレッジ用のライブラリを入れる
  • OpenCover
  • ReportGenerator

f:id:kouki_hoshi:20170520052049p:plain
f:id:kouki_hoshi:20170520052057p:plain

プロダクト参照をテストプロジェクトに設定

f:id:kouki_hoshi:20170520052306p:plain
f:id:kouki_hoshi:20170520052310p:plain

テストコード作成

今回は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]);
		}

	}
}

f:id:kouki_hoshi:20170520052339p:plain

カバレッジ用のバッチを作成する

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の条件を記載。

f:id:kouki_hoshi:20170520052428p:plain
f:id:kouki_hoshi:20170520052437p:plain
f:id:kouki_hoshi:20170520052439p:plain

なお、Visual Studio で保存すると、なぜかBOM付きで保存されるので、テキストエディタでBOMを取らないと動かない。
上記は、UTF-8 BOMなし形式で動作確認している。

バッチ仕様

このバッチは、XXXがプロダクトのプロジェクト名だとすると、XXX.Testをテストプロジェクトとするように定めている。
参照dllはプロダクト・テストともDebugで出力されるものを使用する。

使い方

プロジェクトのビルド

最終的にdllを見ることになるので、両プロジェクトをビルドしておく。
f:id:kouki_hoshi:20170520052519p:plain

パッケージマネージャー コンソールで以下のバッチコマンドを実行する
PS> .\Sample.Test\coverage.bat

f:id:kouki_hoshi:20170520052540p:plain

以下のようなレポートが表示されれば成功。
f:id:kouki_hoshi:20170520052556p:plain

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 でリポジトリに変更が反映できるまでの確認を行う。

設定の流れ

  1. AWS CLI インストー
  2. リモートリポジトリのアクセスユーザ設定
  3. リモートリポジトリ作成
  4. ローカルリポジトリにリモートリポジトリ紐づけ

AWS CLI インストー

以下にアクセス。
AWS コマンドラインインターフェイス | AWS

MSIで提供されているため、MSIダウンロード。
f:id:kouki_hoshi:20170406013207p:plain

ダウンロードしたMSIをインストール。デフォルト設定で問題なし。
インストール完了後は、Git Bashを開きインストール確認。

aws --version

以下のコマンドを叩いてバージョン情報が出ればインストール完了。
f:id:kouki_hoshi:20170406013619p:plain

リモートリポジトリのアクセスユーザ設定

AWSにアクセス
クラウドならアマゾン ウェブ サービス【AWS 公式】
「セキュリティ認証情報」を選択
f:id:kouki_hoshi:20170406004735p:plain
(ログイン画面が出たらログインすること)
「IAMユーザの使用開始」を選択
f:id:kouki_hoshi:20170406005006p:plain
ユーザ作成
f:id:kouki_hoshi:20170406005223p:plain
f:id:kouki_hoshi:20170406005505p:plain
グループが存在しなかった場合は作成。
f:id:kouki_hoshi:20170406010431p:plain
グループに対する権限設定は後で変更できる。今回は管理者権限を付与。
f:id:kouki_hoshi:20170406033701p:plain
グループができるので、選択して「確認」へ。
f:id:kouki_hoshi:20170406033731p:plain
f:id:kouki_hoshi:20170406010946p:plain

ユーザ作成に成功したら、念のために認証情報をダウンロードしておく方が良いと思う。
なお、中身はアクセスキーIDとシークレットアクセスキーの情報が書いてある。
f:id:kouki_hoshi:20170406011004p:plain

リモートリポジトリ作成

サービスから「CodeCommit」を選択。リポジトリがないとスタートページが出るので「Get Started」クリック。
f:id:kouki_hoshi:20170406011915p:plain
f:id:kouki_hoshi:20170406011929p:plain

初回ならレポジトリ作成しろって言われるので、入力。
f:id:kouki_hoshi:20170406011957p:plain

現在では、Windowsではなぜか HTTPSしか選択できないので、下記設定にする。
f:id:kouki_hoshi:20170406012021p:plain

ローカルリポジトリにリモートリポジトリ紐づけ

以下を参考に実施。
Code Commitの使い方 - Qiita

AWS認証情報をローカルに登録。

profile=codecommit
aws configure --profile $profile

必要な情報を入力する。
f:id:kouki_hoshi:20170406021806p:plain

AWS認証情報登録の確認。

cat ~/.aws/credentials
cat ~/.aws/config

f:id:kouki_hoshi:20170406022938p:plain

今回は、ローカルリポジトリを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

f:id:kouki_hoshi:20170406024845p:plain

リモートリポジトリ宛先取得。
f:id:kouki_hoshi:20170406035247p:plain

リモートリポジトリ登録。

url=https://git-codecommit.us-west-2.amazonaws.com/v1/repos/JUnitSample
git remote add origin $url

リモートリポジトリ設定確認。

git remote -v

f:id:kouki_hoshi:20170406025649p:plain

設定完了、挙動確認

ローカルの内容をリモートリポジトリにアップロード。

echo "AAA" > a.txt
git add -A
git commit -m "First Commit."
git push origin master

CodeCommitにも a.txt が反映されればOK。
f:id:kouki_hoshi:20170406040031p:plain

上手くいかなかった場合...

git push 時に403のエラーコードが出た場合は、以下の原因が考えられる。

  • 認証設定が上手くいっていない
  • 作成ユーザに適切な権限がなく実行できない


私は、git push 時、認証情報用のプロンプトが現れて、
正しいアクセス情報を入力しても403エラーになる事象が起こった。
調べてみると、GitのCredential Managerが有効になっていたためだった。
Troubleshooting AWS CodeCommit - AWS CodeCommit
※インストール時デフォルトでは有効になっている。
f:id:kouki_hoshi:20170406041237p:plain

おまけ

シェルを作成してみた。
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とは何かを説明する。

  • 15の倍数なら「FizzBuzz
  • 3 の倍数なら「Fizz
  • 5 の倍数なら「Buzz」
  • それ以外は その数値

を言い合うゲームであり、これをプログラムで標準出力する。
簡単のため、入力はなしとし 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

C#版を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 上に作る

概要

LISP の流れを組み、Java VM上で動作する関数言語の Clojurecygwin 上で使えるようにする。
少し苦戦したため、それをメモ。

構築で参考にした記事

Windows でも Clojure がしたい! - Qiita

シェルをダウンロード

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 は内部でソースファイル、オブジェクトファイルの更新時間を見てるようで、
タイムスタンプ更新しないとコンパイルしてくれないため。

ラッパースクリプトを .bashrc に追加。

毎回 Haskell のためだけに環境設定いじるのが微妙だったり面倒だったりする場合は、cygwin 起動時に読み込まれる「~/.bashrc」に
以下のようにラッパースクリプトを作成する。これだと、ghc を使用するときのみLANGを変更してくれる。

GHC_PATH='which ghc'
function ghc() {
  local LANG='C.UTF-8'
  "$GHC_PATH" $@
}