愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

WindowsのGUIアプリケーションを外部 (Linuxホスト) から実行したいんだ。どうしてもだ。

なんでそんなことをしたくなったのかは、聞かない約束だ。

しかるべきポートを開いておけば、リモートから何らかのコマンドを叩くこと自体は簡単にできる。WinRMを使ってもいいし、それこそsshdを入れてもいい。

しかしそれ系のリモートコマンドだと、GUIアプリケーションを実行することは出来ない。これは考えてみればあたり前で、GUIアプリケーションを動かせるようなセッション(ローカルでのログインなり、リモートデスクトップでのログインなり)とリモートコマンド用のセッションは分かれている。ここではセッションという言葉を適当に使っているけど気にしない。

発想を変えて、リモートからコマンドを送らずにサービス経由で特定の時間にGUIアプリケーションを起動しよう!と思ったところでこれはうまくいかない。最近のWindowsではサービスからGUIアプリを立ち上げることはできない。これをSession 0 Isolationとかいうらしい。

Application Compatibility: Session 0 Isolation

タスクスケジューラから実行する場合でも、やっぱり何らかのログインセッションがない状態で突然GUIアプリを立ち上げたりすることはできない。

ここに、PsExecというツールがある。

blog.treedown.net

これを使えば、特定のログインセッションにひも付けた形でアプリを起動できる。(沢山のポートを開放しなくちゃいけないのが残念だけど...)

つまり、「何らかのログインセッションを常時張っておくことを許容する」ならば、PsExec経由でGUIアプリを叩けるということだ。この場合のログインセッションは別にリモートデスクトップ接続でも構わないので、単につなぎっぱにしておけばよい。

セッションのIDはcmd.exeから

query session

で取れる。

残念ながらPsExecはWindowsでしか動かないツールなので、Linuxでは使えない。実はwinexeというそれっぽいツールがあるのだが、

orebibou.com

これにはPsExecのようにセッションIDを指定するような機能はない。つまり、例によってリモートからWindowsコマンドを叩けるだけのツールなのだ。が、しかし。

操作されるWindowsの方にPsExecのバイナリを置いておけば、それをwinexeから叩くこと自体は可能だ。つまり、winexe経由でPsExecを叩くことによりLinuxホストからWindowsのGUIアプリケーションを立ち上げることができる。もちろん、何らかのログインセッションを事前に張っておくこと前提で。

winexe -U "{username}%{password}" //{windows_ip_address} 'C:\PsExec \\localhost -u {username} -p "{password}" -i {session_id} notepad'

やった!notepadが起動した!

よかったですね。

h8300の開発環境はMacで作らない方がよい (12ステップで作る組み込みOS自作入門)

最近、この本をやってる。

12ステップで作る組込みOS自作入門

12ステップで作る組込みOS自作入門

MacでH8開発環境構築 - 12ステップ本を試す - satfyの日記

上記ブログのように頑張ればMacで開発できるのかもしれないが、総じて言えば今のMacでクロスコンパイル環境を整えようとするのは時間の無駄であることが分かった。

理由は例によってgccの中味がclangとかになっててめんどいから。

まあちゃんとやれば最終的には何とかなるのかもしれないが、とりあえず構築するならVirtualBox上でLinuxを動かして、USBシリアル変換ケーブルをVM側でも認識するようにした方がよっぽど早い。

なお上記の本にはcuやMinicom, Kermitなどいくつかのツールが紹介されているが、シリアル接続に使う端末エミュレータはscreenがよいと思う。

理由としては、僕がVMにsshして開発してるので、cuだと~(チルダ)がsshに取られてなんかどうにもめんどくさくなってどうにもならなくったので、screenを使うと普通になんとかなったというのと、やっぱり端末エミュレータとして頑張って開発されているので変なツールより洗練されていて使いやすいというのがある。(他のプロセスを起動するコマンドは~Cとか言われましても的な)

こういう本をやると、C言語が何のために作られたのかがよく分かる。

JacksonでJSONの一部だけdeserializeさせないための設定

リクエストボディで受け取ったJSON全体はオブジェクトにマッピングして欲しいけどもその一部分だけJSON文字列そのままにしておいて欲しい(= デシリアライズして欲しくない)というケースがある。例えば、JSON-RPCを実装していてparamsの型がmethod依存で変わってしまうような場合。

こうやって

import com.fasterxml.jackson.core.JsonParser;                                                                            
import com.fasterxml.jackson.databind.DeserializationContext;                                                                           
import com.fasterxml.jackson.databind.JsonDeserializer;

public class KeepAsJsonDeserializer extends JsonDeserializer<String> {                                                                                                                                                                                                            
    @Override                                                                                                                            
    public String deserialize(@NotNull JsonParser p, DeserializationContext ctxt) throws IOException {                                   
        return p.getCodec().readTree(p).toString();                                                                                      
    }                                                                                                                                    
}  

こうする

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

public class JSONRPCRequest {                                                                                                            
    String jsonrpc;                                                                                                                      
    String method;                                                                                                                       
    int id;                                                                                                                              
    @JsonDeserialize(using = KeepAsJsonDeserializer.class)                                                                               
    String params;                                                                                                                       
}     

よかったですね。

Mockitoの新機能を使ったモダンげな使い方

Javaのmock用ライブラリは探せば色々あるが、一番よく使われてそう(自分調べ)ってことでMockitoを使っている。

http://mockito.org/

Mockitoはそのロゴからいっても「モキート」「モッキート」と読むのが正しいのだろうけど、心の中ではいつでも「モック伊藤」「モック伊藤」と呼んでしまう。

それはさておき、Mockitoについて日本語で書かれた記事は「mock()でモックしてverify()でベリファイするんやで〜」くらいで留まっているものが多そうに見受けられたので、新し目の機能を使ってこういう風に書くのもよさげかな、とここに記しておく。

参考: Mockito (Mockito 2.0.33-beta API)

mock()ではなく@Mock, spy()ではなく@Spyをつかう

以下のようにmock()メソッドを使ってmockを作ることも出来るのだが、

MyService service = mock(MyService.class);

Mockitoにはアノテーションでmockを作成する機能があるため、テストクラスの最初にフィールドとして以下のようにして書いておく。

public class MyServiceTest {
    @Mock
    MyService service;
}

こうすると、あるオマジナイを書いておくことでMockitoが各テストケースの前にmockを作成してくれるようになる。(オマジナイについては後述) spyについても同様のことが可能。

answerとかもちゃんと指定できる。

public class MyServiceTest {
    @Mock(answer = Answers.RETURNS_DEEP_STUBS;
    MyService service;
}

mock()やspy()を使っても同様のことが出来るのになぜこちらを選ぶのか。それはテストのコードを初めて見た人が「このクラスのテストではこいつらをmockする」とすぐに理解しやすいと考えるから。もちろん各テストケースの冒頭にmock(),mock()と繰り返し書いてあってもちゃんと読めば分かるが、クラスの先頭にまとめて書いてあった方が「テストケース共通でこいつらはmockされている」というのが分かりやすい。

@InjectMocksを使ってDIしてもらう (Whiteboxは使わない)

テスト対象のクラスが別のクラスのインスタンスに依存している、つまりprivateフィールドに別のクラスのインスタンスをコンポジットする感じになっている、というのはあるあるケースだと思う。

public class MyService {
    @Inject
    private OtherService otherService;
}

依存先クラスの単体テストは別に書くので、こいつもmockして置き換えたいなーと思うのが人情なのだが、これをMockito付属のWhiteboxというユーティリティクラスを使って書くとこんな感じになってしまう。

OtherService otherService = mock(OtherService.class);
Whitebox.setInternalState(service, "otherService", otherService);

フィールド名の文字列による指定がいただけない。これは@InjectMocksで置き換えられる。

public class MyServiceTest {
    @Spy
    @InjectMocks
    MyService service;

    @Mock
    OtherService otherService;
}

こう書くと、MyServiceのフィールドにOtherServiceクラスのmockされたインスタンスがDIされる。 これも先程と同様に、手続き的に行うよりも意図が分かりやすい。

どういった種類のインジェクションが可能なのかはInjectMocks (Mockito 2.0.33-beta API)を参照。

MockitoRuleを使う

以上の@Mock, @Spy, @InjectMocksを動作させるためにはオマジナイというか初期化処理が必要なのだが、それをJUnit4のRuleで行うためのMockitoRuleが用意されているのでそれを使う。

public class MyServiceTest {
    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();

    @Spy
    @InjectMocks
    MyService service;

    @Mock
    OtherService otherService;
}

Matchersを使う

今まで書いたのとは若干毛色が違うが、「テキトーな値」を指定するためのMatcherが色々Mockitoには用意されているのでデータをわざわざ用意したくない場合はそれを使う。

doReturn(1).when(this.service).doSomething(anyString()); // どんなStringが渡ってきても1を返す

any(Class clazz)やisNull()などなど、おおむね想像通りのMatcherが用意されている。

Matchers (Mockito 2.0.33-beta API)

注意点として、Matcherを使った引数と通常の引数を混ぜて指定することは出来ない。 つまり以下のように書いてしまうとMockitoに怒られてエラーになる(ちなみにMockitoのエラーメッセージは結構親切だと思う)

doReturn(1).when(this.service).doSomething2(anyString(), 1);

それだと不便じゃね?と思うかもしれないが、そういう場合はeq()を使って引数をMatcherに統一すればよい。

doReturn(1).when(this.service).doSomething2(anyString(), eq(1));

AdditionalMatchersをインポートすれば、配列に対するMatcherなども使えるようになる。

AdditionalMatchers (Mockito API)

今までのまとめ

以上のことをまとめた上で更にちょっと付け足すと、テストクラスは大体以下の様な書き方になる。

public class MyServiceTest {
    // 初期化のためのおまじない
    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();

    // テスト対象のクラス
    @Spy
    @InjectMocks
    MyService service;

    // テスト対象のクラスが依存している(フィールドに持っている)クラス
    @Mock
    OtherService otherService;

    // テストケース間で共通のデータ
    long userId = 1L;

    @Before
    public void setUp() {
        // テストケース間で共通のモック処理はここでやっておく
        when(this.otherService.foo(anyString()).thenReturn("");
        when(this.otherService.bar(any(MyBean.class)).thenReturn(1);
    }

    @Test
    public void testDoSomething() throws Exception {
        // 各テストケースで固有のモック処理はここでやる
        doReturn(1).when(this.service).doSomething(anyString(), eq(1));

        // assertion
    }
}

好みの問題もあるだろうが、各テストケース内に色んなものをダラダラっと書くよりは「このクラスのテストではこうします」という意図は伝わりやすくなるかと思う。

JavaでiPhoneからアップロードされた画像を(再)回転させる

iPhoneからアップロードした画像をPCで表示すると思ったのと違う向きで表示されるという事象は、よく知られているように、iPhoneが本来横向きの画像にexifのorientationをかました上で縦に表示しているから発生する。

blog.isao.co.jp

これに真面目に対応する場合は以下のようなコードを書くのが正解っぽいが、

mumumu.github.io

多少サボったコードでもいいならThumbnailator (https://github.com/coobird/thumbnailator) を通してしまうのが一番楽かと思う。

Thumbnails.of(inputStream).scale(1).toFile(file);