ironおっpythonをnunitと絡めると垂れ落ちて死亡(?)
NUnitのテストクラス中で、IronPythonを利用してニャンニャンしようかと思ったのですが、どうも落ちる。
NUnit と IronPython は相性が悪い模様……? なお、NUnitは GUI/Console でも、x86指定が有るヤツ/無いヤツ問わず全てで「署名がうんこです。糞して寝ろ」って落ちます。 何故や…いや確かに、お腹痛かったけどさ…
同じ環境下での解決方法は今のところ見つかっておりませぬ…。 4時間ぐらいほっといたコーヒーがアカンかったのだろうか…
バージョンとか
- Visual C# 2008 Express Edition
- .NET Framework 3.5 SP1
- NUnit 2.6.1
- IronPython 2.6.1
…色々大人の事情があるんですえぇ……
エラー
SetUp : System.IO.FileLoadException : ファイルまたはアセンブリ 'Microsoft.Scripting, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'、またはその依存関係の 1 つが読み込めませんでした。厳密な名前の署名を確認できませんでした。アセンブリが故意に変更されているか、または適切な秘密キーを使用した完全署名ではない、遅延署名がされています。 (HRESULT からの例外: 0x80131045)
コード
using System.Text; using System.Net; using System.Collections.Specialized; using System.Collections.Generic; using Microsoft.Scripting.Hosting; using NUnit.Framework; namespace A { [TestFixture] class Programs { // コンソールアプリケーションあたりにして、単独で実行すると落ちない public static void Main(string[] args) { Programs p = new Programs(); p.ClassSetup(); } [TestFixtureSetUp] public void ClassSetup() { // ここで和式便器からうんこ漏らす ScriptEngine engine = IronPython.Hosting.Python.CreateEngine(); } [SetUp] public void Setup() { } [TearDown] public void TearDown() { } [TestFixtureTearDown] public void ClassTearDown() { } [Test] public void Test00() { } }; };
プロジェクトのアセンブリ参照
- IronPython
- IronPython.Modules
- Microsoft.Dynamic
- Microsoft.Scripting
- Microsoft.Scripting.Core
- Microsoft.Scripting.Debug
- nunit.framework
- System.
- System.Core
- System.Data
- System.Data.DataSetExtension
dll 的には
- IronPython.dll
- IronPython.Module.dll
- Microsoft.Dynamic.dll
- Microsoft.Scripting.Core.dll
- Microsoft.Scripting.dll
- Microsoft.Scripting.Debugging.dll
問題無かった環境
- Visual C# 2010 Express Edition
- .NET Framework 4.0 (SP??)
- NUnit 2.6.1
- IronPython 2.7.5
では、問題有りませんでした。 「今更 2008 (しかもEE) 使ってるおめーの環境がうんこなんだよ」って、はいすいませんすいません…
【ぼやき】catchって抜けられないんスよねそういえばあああ
try { funcNanika(); } catch(Exception ex) { if(ex is ObjectDisposedException) break; // ココ。唯一の問題無い想定の例外なので正常系として処理。finally に飛ぶ Log(ex); } finally { funcFinally(); }
とかできると嬉しいなぁってシーンに遭遇しました。
…いやまぁ ObjectDisposedException を別口で catch しろよって話なんですけどね!怠けてるんじゃねーよって話なんですけどね!!
DeploymentItem属性に踊らされて死亡
このお話の結論
[TestMethod] [DeploymentItem(@"img\src.bmp")] public void Test00() { ... }
とか書いた場合 "img\src.bmp" がコピーされるタイミングは、テスト自体を実行した時である。 その関数が実行される直前にコピーではない! あくまでテスト自体を実行開始した時である。
TestInitialize とか ClassInitialize とか AssemblyInitialize とかでブレーク貼ると、止まった時には既にファイルがコピーされているのだ…!
というわけで、(Test|Class|Assembly)Initialize で何かのファイルに依存する場合は、テキトーな TestMethod に合わせて DeploymentItem を書いておけば良いかなと思います。 もしくは、[TestMethod] void ResourceInitializer() とか DenependResourceDeployer() とか、それっぽい名前をもちつつ中身空っぽのダミーのテスト関数作ってそこにモリモリ書いていくとか。
…えー…… ('A`)
クラスに DeploymentItem属性 つけられてファイルコピーもされました。
[TestClass] [DeploymentItem(@"img\src.bmp")] public class TestClass { .... }
…えぇぇぇ… ('A`)('A`)('A`)
ことの始まり
とあるクラスのメンバ関数をテストを行いたかったのですが、そのクラスはコンストラクタで Image が要求さていました。この Image はファイルから読み込んで渡してあげれば良いのですが、定数的に変化しない内容である為テスト毎にファイルを読み込むのもダサい(ぉ*1 クラスのインスタンスはテスト毎に作り直すのは良い*2としても、Imageについては一旦読み込んだらそれ使い回すべさーと思い立ったは良いのですが………
ClassInitialize で死亡
テストクラス中1回読めばよかったので ClassInitialize でファイルを読み込むようにしました。
[ClassInitialize] [DeploymentItem(@"img\src.bmp")] public static void ClassInitializer(TestContext context) { _srcImage = Image.FromFile("src.bmp") }
ファイルが無ぇってさ
DeploymentItem は TestMethod でしか有効でないらしいです。 嘘だろ承太郎!? じゃぁ自分でファイル持ってくるかーとか考えるわけですが、この ClassInitializer の時点で詰んでる。
- カレントディレクトリが TestResults の下。[c:\\フロニャルド\\TestResults\\Deploy_millhi 2015-09-29 11_19_39\\Out] とか。
- ファイルコピー元のディレクトリを引っ張ってこれない。 テストプロジェクトのあるディレクトリとか、その下の [bin\Debug] とか取れない…!
- context の中に期待する情報があると思った? 残念! TestResults 以下のモノしか無ぇよ!!
どうすんねん…
ってか、じゃぁテスト実行時に元テストプロジェクトのパスが取得できないと、TestMethod の DeploymentItemさんは、どーやってファイルコピーしてきてるのよ。 どこから元プロジェクトのパス引っ張ってきてるのよ? UnitTesting名前空間以下によさ気なクラスがあるわけでもなさそうだし…まさかのpdbからとか? …TestResults 以下にんなもんコピーされてないよ? 上上下下左右左右BA 押してもダメだったよ? ってか俺R右下Y下右下左右下右右AA派だよ?*3 ソレがダメすかもしや…!?
…と、色んな所にブレーク張りつつ調査した結果、ClassInitialize でブレークした時点で TestMethod の DeploymentItem で指定したファイルが [TestResults] フォルダ以下にコピーされてるっていうねもうね。 DeploymentItemさんおめーその関数呼び出した時にコピーしてるんじゃねーのかよ!!おのれ!!
というわけで、ファイルはVisualStudioでテスト開始した際にコピーされると。そのタイミングであれば、Visual Studio 辺りがニャンヤンしてファイルコピーできそうですねと。はい解散。
PLINQ の Aggregate() で死亡
RGBの色データが入った画像データ配列(正確にはunsafe中のポインタ)を集計したい。
…というシーンが発生。 for でぐるぐる回して集計するのもアリといえばアリですが…当然ながらカッコ悪い。C# なら黙って LINQ だろゴルァと*1。丁度、集計する為の関数 Aggregate() も定義されているし、なにより AsParallel() で手軽に並列化も可能と聞いた! やったねタエちゃん! 暇してるCPUコアに鞭打てるよ!!
とはいえ
集計方針が RGB 個別にニャンヤンする感じなので、seed に R,G,B メンバを備えたクラスを new RGB() して渡すわけですが……参照型渡したら、それ、並列処理できんの…? どう考えてもできないよね…? それとも、PLINQが超賢くなんかやってくれるの…?
正直良くわからんので、とりあえずググったり実験的なコード書いてみた所、最終的にこうなった!
パラレったー
threadIDs の要素数がちゃんとCPUコア数とおなじになったよ! 処理時間も体感レベルで明らかに短かったよ!! すごい!!
class Result { public long R = 0; public long G = 0; }; static public void test() { const int LENGTH = 10001; int[] arr = new int[LENGTH]; for (int i = 0; i < LENGTH; ++i) { arr[i] = i; } Result r = ParallelEnumerable.Range(0, LENGTH).Aggregate( () => new Result(), (aggr, i) => { aggr.R += arr[i]; aggr.G += ComputeG(arr[i]); return aggr; }, (aggr1, aggr2) => new Result { R = aggr1.R + aggr2.R, G = aggr1.G + aggr2.G }, (aggr) => aggr ); arr[0] = 0; // ブレークポイント設定用の何か } static public long ComputeG(int i) { threadIDs.Add(System.Threading.Thread.CurrentThread.ManagedThreadId); System.Threading.Thread.Sleep(1); return i; } private static HashSet<int> threadIDs = new HashSet<int>();
失敗 // 最初に書いたコード
なんも考えずに「AsParallel() したらとりあえず並列処理してくれるんでしたっけー?」的に書いたコード。 threadIDs の要素が1つで死ぬ。 シリアルじゃねーじゃねーか!
class Result { public long R = 0; public long G = 0; }; static public void test() { const int LENGTH = 10001; int[] arr = new int[LENGTH]; for (int i = 0; i < LENGTH; ++i) { arr[i] = i; } Result r = Enumerable.Range(0, LENGTH).AsParallel().Aggregate( new Result(), (aggr, i) => { aggr.R += arr[i]; aggr.G += ComputeG(arr[i]); return aggr; } ); arr[0] = 0; // ブレークポイント作る用の何か } static public long ComputeG(int i) { threadIDs.Add(System.Threading.Thread.CurrentThread.ManagedThreadId); System.Threading.Thread.Sleep(1); return i; } private static HashSet<int> threadIDs = new HashSet<int>();
失敗 // ParallelEnumerable に期待する
Enumerable じゃなくて ParallelEnumerable なんてのがあるんスか!こっちじゃないと根本の Range() が並列化しないとかそんな話すかもしや!? と期待して実行。 threadIDs の要素が1つで死ぬ。 クソぁ!!
class Result { public long R = 0; public long G = 0; }; static public void test() { const int LENGTH = 10001; int[] arr = new int[LENGTH]; for (int i = 0; i < LENGTH; ++i) { arr[i] = i; } Result r = ParallelEnumerable.Range(0, LENGTH).Aggregate( new Result(), (aggr, i) => { aggr.R += arr[i]; aggr.S += ComputeS(arr[i]); return aggr; }, ); arr[0] = 0; // ブレークポイント作る用の何か } static public long ComputeS(int i) { threadIDs.Add(System.Threading.Thread.CurrentThread.ManagedThreadId); System.Threading.Thread.Sleep(1); return i; } private static HashSet<int> threadIDs = new HashSet<int>();
ってか
やっぱり seed に与えてる new Result() じゃ無理ですよね…とぐぐり始めた所 Parallel Aggregation | Microsoft Docs の "Using PLINQ Aggregation with Range Selection" を発見。 サンプルソースを見れば一発でした。 なるほど。
ちなみに ParallelEnumerable.Aggregate Method (System.Linq) | Microsoft Docs を使ってるわけですが、なんかもうまず見た目でマジうっへりなんですけどこのリファレンス
いやぁ、PLINQ って本当にいいものですね(※流れてくる哀愁ただようトランペットBGM
*1:※偏見です
catch「俺に捉えられぬものは……無いッ!」
try { なにか(); } catch { // ↑ カッコ省略可…だと…!? throw; }
mjd...
AndroidJavaObjectで死亡
Unity では C# Script中の AndroidJavaObject を経由して、Javaのオブジェクトの関数とか呼べたりするのは良いのでーすーがー……
// Java package net.p_pit.rozen public class Tamagoyaki { pubilc int getAijyoByMicchan() { return Integer.MAX_VALUE; } }; public class Kashira { public int getNumber() { return 2; } public Tamagoyaki getTamagoyaki() return null; } };
// Unity 5.0.1 ぐらい using UnityEngine; public class UseJavaClass : MonoBehaviour { void Start() { using(AndroidJavaObject kana = new AndroidJavaObject("net/p_pit/rozen/Kashira")) { // OK int number = kana.Call<int>("getNumber"); // Exception: JNI: Init'd AndroidJavaClass with null ptr! using(AndroidJavaObject tamago = kana.Call<AndroidJavaObject>("getTamagoyaki")) { if(tamago == null) { } else { } } } } }
Java側の関数をコールした際、戻り値の型がプリミティブ型であれば問題ないんですが、Java object を返す場合 AndroidJavaObject を指定したところで落ちるっていう。
AndroidJavaObjectのコンストラクタを逆アセンブルすると次のような実装になってるようで
internal AndroidObject (IntPtr jobject) : this() { if(jobject == IntPtr.Zero) { throw new Exception ("JNI: Init'd AndroidJavaClass with null ptr!"); }
えー(苦笑
個人的には、Java側が null 返したら、Unity側でも null返してほしい所ですけどねぇ…。 結局、Java側でnullを返した際にUnity側で受け取る方法がわからず、必ずインスタンスを返す方向で無理やり実装しました。
Unity側では Java側で nullを返す事がある関数の呼び出しを行ってはいけないって所なんですかねぇ。うむむ…