2021年5月23日

[android] Service (2) - bindって何よ

前回に引き続き、Android OSのServiceについてだ。

うちのサイトは「技術日記」なので、学習としては向いていないと思う。
その代わり、正しくなかったことも書いているはず。
まあ、ネットで調べながらだとそういうことも少なくなってしまうが。


で、Serviceだ。

AndroidStudioでMyServiceというサービスを組み込んでみたのだが、その状態でアプリを起動させてもサービスが立ち上がった気配がない。気配というのは、overrideできる関数を全部オーバーライドしてLogを埋め込んだもののlogcatに何も出力されなかったからだ。

というわけで、ServiceはActivityからの起動が必要となる。
「別のアプリ コンポーネントがサービスを開始でき」と書いてあるので、別にActivityから起動させる必要はないのだが、知らないサービスを起動させることはできないだろうから、まあだいたいはServiceとセットになったActivityから起動させるのが妥当だろう。

01: package com.example.sample0
02: 
03: import android.content.Intent
04: import android.os.Bundle
05: import androidx.appcompat.app.AppCompatActivity
06: 
07: class MainActivity : AppCompatActivity() {
08:     override fun onCreate(savedInstanceState: Bundle?) {
09:         super.onCreate(savedInstanceState)
10:         setContentView(R.layout.activity_main)
11: 
12:         intent = Intent(this, MyService::class.java)
13:         startService(intent)
14:     }
15: }

Kotlinだとnewは不要なのは知っていたのだが、intentの書き方が分からんかった。
「インテントとインテントフィルタ」に書いてあったしエラーにならなかったので、これでよいのだろう。

 

MyService.ktはオーバーライドできそうな関数にログを埋め込んだだけ。

01: class MyService : Service() {
02:     private val logTag: String = "MyService"
03: 
04:     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
05:         Log.d(logTag, "onStartCommand")
06:         return super.onStartCommand(intent, flags, startId)
07:     }
08: 
09:     override fun onBind(intent: Intent): IBinder {
10:         Log.d(logTag, "onBind")
11:         TODO("Return the communication channel to the service.")
12:     }
13: 
14:     override fun onCreate() {
15:         Log.d(logTag, "onCreate")
16:         super.onCreate()
17:     }
18: 
19:     override fun onDestroy() {
20:         Log.d(logTag, "onDestroy")
21:         super.onDestroy()
22:     }
23: }

これでActivityを立ち上げると、onCreate, onStartCommandの順でlogcatが出てきた(エミュレータで起動)。

そのまま放置していたが何も変わらない。
そしてアプリを閉じる(Androidのホーム表示)にすると、1分後にonDestroyがlogcatに出てきた。

たった1分かー。

[android] Service (1)

では、AndroidStudioを使ってServiceを追加してみよう。
特に何かしたいわけでもないので、毎分logcatで何か出し続けるようにしてみる。そうすればいつアプリが終了させられたのかもわかりやすいんじゃなかろうか。

 

デフォルトをちょっと変更したプロジェクトの設定はこんな感じ。

image

image

image

image

家にあるAndroid端末で一番古いのがNougatなので、Min SDK Versionはこの辺にしている。

 

プロジェクトを新規作成して取りあえずビルドしてみたのだが、トップディレクトリにあるbuild.gradleのjcenter()でwarningが出ていた。

JCenter サービスの更新  |  Android デベロッパー  |  Android Developers
https://developer.android.com/studio/build/jcenter-migration

既存のものは無期限にダウンロードできます、といっても「閉鎖されるまでは」という条件が付くだろう。メンテナンスしないとしても電気代はかかるし、自社サーバだったらストレージが故障したりするし、クラウドだったらストレージの費用も馬鹿にならんし。


Serviceを追加する!と気合いを入れずとも、AndroidStudioならメニュー操作で基本的なところはやってくれそうだ。

image

2つある・・・?

"Service"の場合

image

"Service(IntentService)"の場合

image

ちなみにActivityのあるディレクトリ?以外で実行するとどこに組み込むかのコンボボックスが増えていた。
main, debug, releaseの3択だったのだが、特定のVariantにだけ追加する(debug, release)か、全部に追加する(main)かを選んでいるのだろうか。

image

Build Variantsを追加すると増えていたので、たぶんそうなんだろう。

image


まずは、ただのServiceを追加した場合。

 

ファイルの追加

app\src\main\java\com\example\sample0\MyService.kt

01: package com.example.sample0
02: 
03: import android.app.Service
04: import android.content.Intent
05: import android.os.IBinder
06: 
07: class MyService : Service() {
08: 
09:     override fun onBind(intent: Intent): IBinder {
10:         TODO("Return the communication channel to the service.")
11:     }
12: }

 

AndroidManifest.xmlに追記。

01:         <service
02:             android:name=".MyService"
03:             android:enabled="true"
04:             android:exported="true"></service>


こちらはIntentServiceを追加した場合。

 

ファイルの追加

app/src/main/java/com/example/sample0/MyIntentService.kt

01: package com.example.sample0
02: 
03: import android.app.IntentService
04: import android.content.Intent
05: import android.content.Context
06: 
07: // TODO: Rename actions, choose action names that describe tasks that this
08: // IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
09: private const val ACTION_FOO = "com.example.sample0.action.FOO"
10: private const val ACTION_BAZ = "com.example.sample0.action.BAZ"
11: 
12: // TODO: Rename parameters
13: private const val EXTRA_PARAM1 = "com.example.sample0.extra.PARAM1"
14: private const val EXTRA_PARAM2 = "com.example.sample0.extra.PARAM2"
15: 
16: /**
17:  * An [IntentService] subclass for handling asynchronous task requests in
18:  * a service on a separate handler thread.
19: 
20:  * TODO: Customize class - update intent actions, extra parameters and static
21:  * helper methods.
22: 
23:  */
24: class MyIntentService : IntentService("MyIntentService") {
25: 
26:     override fun onHandleIntent(intent: Intent?) {
27:         when (intent?.action) {
28:             ACTION_FOO -> {
29:                 val param1 = intent.getStringExtra(EXTRA_PARAM1)
30:                 val param2 = intent.getStringExtra(EXTRA_PARAM2)
31:                 handleActionFoo(param1, param2)
32:             }
33:             ACTION_BAZ -> {
34:                 val param1 = intent.getStringExtra(EXTRA_PARAM1)
35:                 val param2 = intent.getStringExtra(EXTRA_PARAM2)
36:                 handleActionBaz(param1, param2)
37:             }
38:         }
39:     }
40: 
41:     /**
42:      * Handle action Foo in the provided background thread with the provided
43:      * parameters.
44:      */
45:     private fun handleActionFoo(param1: String?, param2: String?) {
46:         TODO("Handle action Foo")
47:     }
48: 
49:     /**
50:      * Handle action Baz in the provided background thread with the provided
51:      * parameters.
52:      */
53:     private fun handleActionBaz(param1: String?, param2: String?) {
54:         TODO("Handle action Baz")
55:     }
56: 
57:     companion object {
58:         /**
59:          * Starts this service to perform action Foo with the given parameters. If
60:          * the service is already performing a task this action will be queued.
61:          *
62:          * @see IntentService
63:          */
64:         // TODO: Customize helper method
65:         @JvmStatic
66:         fun startActionFoo(context: Context, param1: String, param2: String) {
67:             val intent = Intent(context, MyIntentService::class.java).apply {
68:                 action = ACTION_FOO
69:                 putExtra(EXTRA_PARAM1, param1)
70:                 putExtra(EXTRA_PARAM2, param2)
71:             }
72:             context.startService(intent)
73:         }
74: 
75:         /**
76:          * Starts this service to perform action Baz with the given parameters. If
77:          * the service is already performing a task this action will be queued.
78:          *
79:          * @see IntentService
80:          */
81:         // TODO: Customize helper method
82:         @JvmStatic
83:         fun startActionBaz(context: Context, param1: String, param2: String) {
84:             val intent = Intent(context, MyIntentService::class.java).apply {
85:                 action = ACTION_BAZ
86:                 putExtra(EXTRA_PARAM1, param1)
87:                 putExtra(EXTRA_PARAM2, param2)
88:             }
89:             context.startService(intent)
90:         }
91:     }
92: }

AndroidManifest.xmlに追記。

01:         <service
02:             android:name=".MyIntentService"
03:             android:exported="false"></service>

なぜか知らんが、AndroidManifest.xmlはさっき追記した <service ...></service> が <service ... /> に置き換えられた。


名前に同じ「Service」が付いているものの、全然似たところがない。

だがIntentServiceはServiceの派生クラスだ。
と思ったら! IntentServiceはAPI 30からdeprecatedとのことだ。代わりに androidx.work.WorkManager とか androidx.core.app.JobIntentService を使うことをおすすめされている。

WorkManager はバックグラウンドタスクのところに出ていた。 JobIntentService は「作業ステータスを報告する」に出てきていた。

 

一つ分かったのは、初学者にとってはclassから調べていくのは効率が悪そうだ、ということだ。「人間とは何か」を調べる命題があったとして、細胞とか動植物から調べ始めるようなもので、間違っているわけではないけど後で調べた方が効率がよさそう、というやつだ。

 

そして・・・私はIntentServiceは忘れることにした。
私のバッファはそんなに大きくないのだ。

[android]サービス

前回、画面を作るのはFlutterとかに任せるとして、OSに依存した部分を覚えようということになった。

まずは基本要素のサービスだろう。
と思ったのだが、ドキュメントのメニューを見て迷いが生じた。

image

サービスはバックグラウンドで動くものだったはずだが...

バックグラウンドタスクというのは、バックグラウンドで動くタスクの総称のようで、サービスがバックグラウンドで動き続けるのと比べると、ワンショットで終わったり、定期的に実行したり、指定した時間に実行したりと、ちょっと意味合いが違うようだ。

意味合いが違うのは、単位が違うからでもあろう。
サービスは「アプリコンポーネント」で、バックグランドタスクは「タスク」。
アプリコンポーネントが指すのは4つしかない。

  • アクティビティ
  • サービス
  • ブロードキャストレシーバ
  • コンテンツプロバイダ

四天王というところか。

サービスを使うかどうかは作るアプリの性質に因るだろう。ユーザに見えない形で処理をしたいことはしばしばあるだろうが、例えばボタンをタップして処理が終わるまで待ち状態のアニメーションを表示して、処理が終わったら画面を更新する、みたいなことをするとしよう。たぶんそれだけであればサービスは使わず、バックグラウンドタスクでよいだろうし、たぶんそれも意識せずにFlutterなどがやってくれるんじゃないだろうか。

サービスを使うのはバックグラウンドで長期的に動いていないといけないような、音楽の再生や通知待ちだとかになるのだろう。通知待ちにして待つもの次第で変わって、場合によってはブロードキャストレシーバとかでよいのかもしれない。


サービスはユーザインターフェースを持たない、と書いてある。私としては音楽再生もユーザインターフェースの一つ(何か操作して音楽が鳴り始めるなら、音楽は操作した結果をユーザに分からせるというUI)と考えてもよいんじゃないかと思うのだが、そういうのは専門じゃないのでよくわからん。組み込みの世界だと、LEDの点滅のさせ方とかビープ音をUIとして扱うからね。

それはともかく。
サービスとしては画面がないということだろう。そして種類として「サービスが動いていることをユーザが直接分かる(音楽が鳴っているとか)」「サービスが動いていることをユーザが分からない」の2種類があると書かれている。

ユーザが直接分かるタイプの場合は、サービスが強制終了させられるとユーザに不便が生じるので、実行が維持されるよう「最善を尽くす」ようだ。善処します、ということかな。

そうじゃないタイプの方は、システム側が好き勝手に終了させてもいいよね、という扱いなのだろう。電力消費や他のアプリを行かすことを考えるとこちらの方が望ましい。だってスマホのアプリを起動するとき、今使っているアプリを終了させてから、とかやる人はあんまりいないだろうからだ。

しかし困ったことに、「一度起動したんだから動いてるよね」と思ってしまうのも確かだ。なので、アプリを作る人はどちらのタイプにせよシステムから終了させられてもよいようにする必要がある。

 

と書くのは簡単なのだが、サービスを使う以上は裏で動き続けたいという気持ちがあるからなので、終わってもらっても困る。アプリ・・・アクティビティが前面に出ているときだけ動けばよいなら終わってもよいのだが(アクティビティの終了以降にサービスが終了するならば)、待ち受けるタイプのアプリで、しかもいつやってくるのかわからないようなタイプだと、待っている間に別のアプリを立ち上げるというのは十分に考えられる。

そのときの捌き方を覚えないと行かんな。

2021年5月22日

[android]久々のAndroid(2021/05/22)

このブログでAndroidのことを記事にしたのは2019/6/10であった。コーディングに関する記事だともっと前だ。

そのくらいAndroidのことをやってなかったのだが、必要に駆られて調べることにした。


2021/05/22時点で、最新のOSバージョンは11。アルファベットはR。12のベータが進められている状況だ。

Android Studioのバージョンは4.2.1が最新。

image

image

image

 

新規プロジェクトを作ろうとするとこうなった。
Nameは私が書き換えたが、デフォルトは"My Application"だったはず。
時代はKotlinなのね......

ちなみに一番下のチェックボックスは、Minimum SDKがAPI 29以上になるとチェックできなくなる。

image


さて、基本的な環境はこれくらいとして、実際に世の人はどうやってアプリを作っているのだろうか?

昔はともかく、最近はAndroidもiOSも両方ともアプリをリリースしたいという要望は多いだろう。特にデバイスと通信して生かすようなアプリであればスマホの種類がどうのこうの関係なく使えるようにしたいはずだ。
といっても、そういうネイティブな機能はOSごとにやるだろうから、画面と遷移

 

よく聞くのはFlutterだ。Dart言語で書くらしい。

Unoというのがスマホだけでなくデスクトップ用のOSもサポートしていそうな気配がする。Xamarinという文字も出てきていたので、MicrosoftのXamarinも元気なのかもしれない。

Unityも聞くね。ゲームエンジンのようだが、UIも全部自前で描画するならばOSの違いも吸収しやすいのだろうか。

Unreal Engineもゲーム向けのようだ。

React Nativeというのもあるな。

それ以外はよくわからない...
個人的には、デスクトップ向けは別に作った方がよいと思うので、ゲームゲームしたものを作るならUnityかUnreal Engine、そうでなければFlutterかReact Nativeがよいような気がする。
どれも使ってないので雰囲気だけだがね。


取りあえず、画面は何か使って作るものとしよう。

そう割り切ると、OSで実装がいるのはOS独自の部分だ。
そして画面に関する部分は提供されるものをそのまま使えばよいと思うので、画面に出てこない部分を知っておけば良かろう。

 

まずはライブラリ。

Jetpackの一部としてAndroidXというライブラリが導入されることになったらしいのだが、そもそもJetpackがわからん。
それにこの話もAPIレベル28以降と書いてあるので、minimim APIがそれより下だったら関係ない・・・?

いや「APIレベル28のリリース以降」という表現が怪しい。
それに「新しいプロジェクトではAndroidXライブラリの使用を推奨」かつ「既存もAndroidXライブラリへの移行を推奨」みたいな書き方をしているところを見ると、少なくとも新規プロジェクトの場合はあまり考えずに使えばよいのか。

そもそもサポートライブラリって何よ?なのだが......将来にAndroid OSのバージョンが上がって該当するclassなどがなくなったとしてもサポートライブラリがうまいことやってくれる、ということだろうか。違う書き方をすれば、今のAndroid OSではサポートされるようになったけれどminimum SDKでサポート外のバージョンを含めたい場合にサポートライブラリが対応していれば使える、というところか。

じゃあJetpackは何なのか?

Android Jetpackってなにもの? - Qiita
https://qiita.com/k_masa777/items/c01c1de6ac763ce5c075

サポートライブラリ(更新停止)+α=Jetpack、Jetpack+β=AndroidX、というところだろうか。
章のタイトルが「サポートライブラリ」だから、今後はAndroid開発をサポートするライブラリというそのままの意味にしてしまいたいのかもしれんね。汎用的な名前は検索しづらいので歓迎しよう。

 

それとサービスか。
表の部分はやってくれるから、バックグラウンドの部分を気にするべきだろう。

iOSもだけど、バックグラウンドで動くことについてはどんどん制限が厳しくなってきてると思う。まあ、動かしたいアプリがたくさんあって、電池の消費がネックになって、OSの機能は強化したくて、となってくるとインストールするアプリについて制限を掛けていくしかないのだろう。

といっても、サービスやらインテントやらは昔からあるので、きっと情報はたくさんあるだろう。


情報を持ってない内容をざっと調べて記事にすると、単なる日記にしかならないことが分かった。
まあ、私の記事は大半が技術日記だから良いのだけどね。

2021年5月2日

[js]はてな、はてなはてな、はてなドット

C言語の人なので、JavaScriptのコードもなんとなく読める。

と思っていたのだが、さっぱりわからんものも存在する。


??, ??=

Null合体、Null合体代入、などと呼ぶらしい。

??

??=

 

まず代入しない方の??から。

A ?? Bという書き方で、Aがnullでもundefinedでもない場合はAを返し、そうでない場合はBを返す。主な使い方なのかわからんが、初期値の設定として使う例が書かれている。

C = A ?? 固定値

みたいな書き方で、Aが初期化済みだったらその値を使い、そうじゃなかったら固定値を割り当てる。

??=は、その代入版のようなものだが、代入されるのは自分が未初期化の場合のみだそうな。


||=, &&=

上の??=に似ていて、条件が合ったときだけ代入される。

||=は自分が偽の場合、&&=は自分が真の場合。


?.

Optional chaining、というそうだ。

接続されたオブジェクトチェーンの深くに位置するプロパティの値を、チェーン内の各参照が正しいかどうかを明示的に確認せずに読み込むことを可能にします

わからんね。

 

 

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
};

const dogName = adventurer.dog?.name;
console.log(dogName);

これだと、adventurerのメンバにdogはいないのだが、参照の正しさを確認しないので、dogNameに代入するときに「adventurer.dog.nameはundefinedだ」ということでundefinedになる。

 

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
};

const dogName = adventurer.dog.name;
console.log(dogName);

この場合は「adventuere.dog自体が存在しない」と代入時に判断するので、console.log()までたどり着かずにエラーとなる。

 

const dogName = adventurer?.dog;
console.log(dogName);

こんな感じでルート項目というか親というかがundefinedの場合は?.であってもエラーになる。参照をチェックしないのはチェーンなので、親はチェックするということだろう。

…などと思ったが、?.の対象となる要素は右側にある名前だ。A?.BだったらBの方に意味があるので、ここだとadventurerの中にdogがあってもなくてもエラーにしないというだけのことだ。

 

const adventurer = {};
adventurer.dog = {name: 'Pochi'};
const dogName = adventurer.dog?.name;
adventurer.dog = {name: 'Tama'};
console.log(dogName);

これは?.とは関係ないのだけど、出力は”Pochi”になった。adventurer.dog.nameはプリミティブな文字列型なのでdogNameに直接代入されたためだろう。adventurer.dogはオブジェクトなので、そっちを代入したなら出力は変更される。

const adventurer = {};
adventurer.dog = {name: 'Pochi'};
const dogName = adventurer.dog;
adventurer.dog.name = 'Tama';
console.log(dogName.name);

 


他にもあるのだろうが、気付いたのはこんなところだ。

2021年4月29日

[golang]GO111MODULEはまだ使う

お仕事でgolangのプロジェクトをビルドしていた。
ビルドするだけなので手順通りにやったつもりなのだが、srcの下にファイルがないから取ってこい、みたいなエラーが出ていた。

実際にないので、go getで取ってくる。
ドキュメントを見ると -d でソースコードをダウンロードしてくれるようだ。

The -d flag instructs get not to build or install packages. get will only update go.mod and download source code needed to build packages.

が、実行して src/ の下を見てもダウンロードされていない。 pkg/mod/ の下にありそうなのだが、そこでは困るのだ。

 

ドキュメントに Legacy GOPATH go get というのがある。 gopath-get と書いてあるのだが、そんなものはない。

module-aware mode と legacy GOPATH mode というのがあるのだろうけど、go get のオプションが違うわけでもないし、何だかよくわからん。

 

そこで出てきたのが、 GO111MODULE=off だ。
これを指定すると src/ の方にダウンロードしてくれた。これが legacy というやつなのかね。

GO111、と妙にバージョンに依存した書き方になっているから不要になったのかと思ったが、まだそうもいかんようだ。全部がgo modを使う世界になったら不要になるとかなのかもしれん。

 

 

あと、gomobileか何かで Androidのをビルドしていたのだけど、そこで i386 でのビルドもしているためか GO386=GO387 はサポートしてない、というエラーになってしまった。
go v1.16からそうなったらしい。

GO386=softfloat、みたいにしておけば回避できるので、今回はそれで逃げよう。

2021年4月24日

[win]AppDataとProgramData

私のPCはWindowsがインストールされている。
Windows10 Proしか今は無いと思うが、Homeでもそんなに変わらんだろう。

何かというと、インストールされたアプリが使用するディレクトリについてだ。
Windowsだとフォルダと呼んだ方が良いのかもしれん。

 

大昔、Windows3.1とかの時代は、たしかINIファイルに全部保存していたような気がする。
3.0Aまでかもしれんが、ちょっと記憶にない。
で、どこからかパーソナルじゃなくてマルチユーザで使う必要が出てきて、レジストリに保存するということになってたんじゃなかったか。
さらに時代は進み、複数OSで同じアプリを動かしたいことになったためかどうかは知らないが、設定ファイルを作る方式のアプリも多いと思っている。

 

Linuxなんかは、ユーザディレクトリの下にドットで始まるファイル名なりディレクトリ名なりで場所を作って、そこに設定を保存している。/etcとかもあるだろうが、とにかくある程度場所が決まっている。

それに対してWindowsの場合、歴史的経緯もあってか設定がどこに保存されているのか分かりづらい。

  • レジストリ
  • ユーザディレクトリの直下
  • ユーザのAppData
  • ProgramData

おそらくこんなところではないだろうか。
たまにルートディレクトリにファイルを残しているのがあるが、もうそういうのはあきらめよう...


Windowsで見えにくいディレクトリとして、%ProgramData%と%AppData%があると思っている。
ここはもう、個人の感想だ。

なんというか、インストールして動かしたことがあるアプリは、基本的に「直接」インストールしたプログラムファイルは削除しても、使っている間に作ったファイルなんかは削除してくれないイメージがある。
そういうファイルたちが残っているのがこの見えないディレクトリたちだと思っている。

いま%ProgramData%のサイズを見てみると4GB近くあったのだ。
256GBの4GBって、結構な占有率なのだよ。256GBもディスクのGB単位だし。

 

さらに腹立たしいのが%AppData%だ。
環境変数名でアクセスすると Roaming のディレクトリしか出てこないけど、実際に見えないのは Local, LocalLow, Roamingの3ディレクトリだ。
それが9GB近く使っている。

そしてこの3つの使い分けがよくわかってないので、怖くて消せないのだ。
ストレージに余裕があるなら残していてもよいかもしれないが、それでも使っていないファイルが残るのは気持ちが悪い。
見覚えのないファイルは全部消してしまえ!!

 

・・・と、いうのを私はよくやる。
削除好きとも言う。
そして、アプリがいくつか起動しなくなるというのもしばしば。そういうのは気にしないで再インストールさせれば済んだこともあったのだけど、最近はもうそういうのが通用しなくなってきている。
時代の流れは残酷かもしれないが、私も時代に対しては冷たくあしらっているかもしれないのでお互い様だ。

 

 

そんなわけで、こういうディレクトリ達をどうしてよいのか困っているところだ。
ストレージに余裕があるなら放置したかもしれないが、余裕はない。
それに、ファイアウォールで不正な通信はなるべく塞ごうとしているものの、限界はあるので削除できるものはしておきたいというのもある。

解決する案は何もないのだが、記事にしておくよいかもしれんので残しておこう。

2021年4月3日

[win][lubuntu]ファイルコピーするとWindows側で変に見える

謎現象で、私の環境だけのような気もするが、メモを残しておこう。

 

Windows10 Pro

image

Lubuntu 20.04

image

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.2 LTS"

 

WindowsでVirtualBoxを立ち上げていて、、LubuntuはそのゲストOSとしてインストールされている。
あまり覚えてないが、たぶんLinux側でsambaをなんかインストールしてるはず。

 

  1. Windows側でLinux側のホームディレクトリをExplorerで開く
  2. Windows側でそこに空ファイルを作る(ここではtest1.txt)
  3. Linux側のPCManFMでそのディレクトリを開く
  4. test1.txtを選択してCtrl+C
  5. 続けてCtrl+V
  6. ダイアログが開くので、File nameを"test2.txt"にしてRenameをクリック
  7. PCManFMでは、test1.txtとtest2.txtが見える
  8. WindowsのExplorerで見ると、test2.txtが変なファイルとして見える

image

lsで見ても、特に違いが無いのだがね。。。

$ ls -la
total 8
drwxrwxr-x  2 xxx xxx 4096 Apr  3 10:31 .
drwxr-xr-x 47 xxx xxx 4096 Apr  3 10:08 ..
-rw-r--r--  1 xxx xxx    0 Apr  3 10:29 test1.txt
-rw-r--r--  1 xxx xxx    0 Apr  3 10:29 test2.txt

 

で、これをLinux側のコマンドラインから "cp test2.txt test3.txt"でコピーすると、test3.txtは普通のファイルとして見えるのだ。

"touch test4.txt" しても普通のファイルに見えるし、PCManFMで test4.txtを同じ手順で test5.txt にしても普通のファイルに見える。

Explorerで test5.txt を Ctrl+C, Ctrl+Vして test6.txtにしても普通のファイルに見えるが、 test6.txtを PCManFMで Ctrl+C, Ctrl+V して test7.txtにすると、それは変なファイルに見える。

 

そう、Windows側で新規にファイルを作った場合で、それを PCManFMでコピーした場合のみ、なのだ。この法則が分からなくて、ときどき変なファイルになってWindows側から編集できなくなっていたのである。

わかってしまえば、Windows側で新規ファイルにならないように操作すればいいだけなのだけど。今までこういうの無かったよなぁ。。。

2021年3月28日

[c/c++]fffでテストを作る

前回の続き。

hiro99ma blog: [c/c++]久々のfff
https://hiro99ma.blogspot.com/2021/03/ccfff.html

 

何でもいいからmeekrosoft/fffを使ってテストを作ってみよう。


fffはgtest(おそらくgoogle testの古いやつ)が入っているので、まずはfakeの部分は忘れてテストを実行するところだけやる。

main関数はgtest側にあるので、テストされる方のファイルはmain()だけは別のファイルにするようにしておくのが良いだろう。まあこれは他のテストを使う場合でも同じかもしれん。

 

hello.h

01: #ifndef HELLO_H__
02: #define HELLO_H__
03: 
04: const char *get_hello(void);
05: 
06: #endif /* HELLO_H__ */

hello.c

01: #include "hello.h"
02: 
03: const char *get_hello(void)
04: {
05:     return "hello";
06: }
  

main.c

01: #include <stdio.h>
02: #include "hello.h"
03: 
04: int main(void)
05: {
06:     printf("%s\n", get_hello());
07:     return 0;
08: }
  

 

Makefile

01: hello:
02:     gcc -Wall -o hello hello.c main.c
03: 
04: clean:
05:     rm -f *.o hello
  

 

こんなファイルたちがあって、get_hello()が"hello"を返すかどうかというテストにしてみよう。

 

testsディレクトリをつくる

なくてもよいけど、本体とテストは別にしておきたいので、tests/というディレクトリを作る。

ついでに、tests/fffというディレクトリも作っておく。

 

fffのファイルをコピーする

meekrosoft/fffをcloneして、いくつかのファイルを作成した tests/fff/ ディレクトリの中に置く。

    • gtestディレクトリまるまる
    • fff.h
    • LICENSE

全部コピーしても良いのだが、最低限いるのはこれらだ。

 

テスト本体

gtest.hを見る限り、テストで使える比較マクロはこれらのようだ。

//    * {ASSERT|EXPECT}_EQ(expected, actual): Tests that expected == actual
//    * {ASSERT|EXPECT}_NE(v1, v2):           Tests that v1 != v2
//    * {ASSERT|EXPECT}_LT(v1, v2):           Tests that v1 < v2
//    * {ASSERT|EXPECT}_LE(v1, v2):           Tests that v1 <= v2
//    * {ASSERT|EXPECT}_GT(v1, v2):           Tests that v1 > v2
//    * {ASSERT|EXPECT}_GE(v1, v2):           Tests that v1 >= v2

と言いつつ、文字列とboolもあるようだ。stdbool.hがいるのかな? そこは試してない。

{ASSERT|EXPECT}_TRUE(v1)
{ASSERT|EXPECT}_FALSE(v1)

{ASSERT|EXPECT}_STREQ(v1, v2)
{ASSERT|EXPECT}_STRNE((v1, v2)
{ASSERT|EXPECT}_STRCASEEQ((v1, v2)
{ASSERT|EXPECT}_STRCASENE((v1, v2)

FLOATもあったし、他にもいろいろありそうだ。

ASSERTとEXPECTの違いは、失敗時に関数内の処理を継続するかどうか。ASSERTで失敗するとそこでreturnして次のテスト関数に進むし、EXPECTで失敗すると次の行以降も実行する(もちろんテストとしてはエラーになるが)。
個人的にはASSERTだけでいいんじゃないのかと思うが、まあこれは内容次第だろう。

 

tests/test_hello.cpp

01: #include "fff/gtest/gtest.h"
02: 
03: extern "C" {
04:     #include "../hello.c"
05: }
06: 
07: class test_hello: public testing::Test {
08: };
09: 
10: TEST_F(test_hello, hello1)
11: {
12:     ASSERT_STREQ(get_hello(), "hello");
13: }
14: 
15: TEST_F(test_hello, hello2)
16: {
17:     ASSERT_STRNE(get_hello(), "hellos");
18: }
19: 
20: TEST_F(test_hello, hello3)
21: {
22:     ASSERT_STRCASEEQ(get_hello(), "HELLO");
23: }

Makefile

ここはcmakeとか他にもいろいろあるかもしれんし、シェルスクリプトでもいいのかもしれん。
自分の好みで。

01: TEST_SOURCES += \
02:     test_hello.cpp
03: 
04: all: mk_fff
05:     mkdir -p build
06:     g++ -o build/tst $(TEST_SOURCES) fff/build/*.o -pthread
07: 
08: mk_fff:
09:     mkdir -p fff/build
10:     cd fff/gtest; $(MAKE) all
11: 
12: test:
13:     ./build/tst
14: 
15: clean: 
16:     cd fff/gtest; $(MAKE) clean
17:     rm -rf fff/build build

 

ビルド&テスト

makeしてmake testすればテストが実行される。

$ make test
./build/tst
[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from test_hello
[ RUN      ] test_hello.hello1
[       OK ] test_hello.hello1 (0 ms)
[ RUN      ] test_hello.hello2
[       OK ] test_hello.hello2 (0 ms)
[ RUN      ] test_hello.hello3
[       OK ] test_hello.hello3 (0 ms)
[----------] 3 tests from test_hello (0 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (0 ms total)
[  PASSED  ] 3 tests.


さて、ここまではfff無しだった。
fffはfff.hだけあればよいのだが、いくつか準備がいる。

まず、fffが使うグローバル変数の確保。
includeは何箇所でやっても良いのだが、1箇所だけDEFINE_FFF_GLOBALSを置く。どこに置くか悩むんだったら専用のファイルを作ってしまえば良かろう。
置き忘れたら fffが無いというリンクエラーになるので、気付くだろう。

 

設定はそれだけで、あとはfake関数を作るだけだ。
マクロはこの2つ。

  • FAKE_VOID_FUNC : 戻り値なし
  • FAKE_VALUE_FUNC : 戻り値あり

VOIDかVALUEかは、戻り値があるかどうかだけだ。戻り値なしも同じマクロにできればよいのだが、有ると無しでは大きく違うので仕方なかろう。

書き方はFAKE_VOID_FUNC(funcname, args...)か、FAKE_VALUE_FUNC(戻り値の型, funcname, args...)で、argsは引数の型だけ。引数が無ければ書かなくて良い。

どうやってマクロで引数を処理しているかというと、FUNC0、FUNC1、...、FUNC20、のようにそれぞれの引数のマクロが用意されているのだった。大変ですな。。。

fake関数を作ると、関数名の後ろに_fakeを付けた構造体の変数が定義される。

  • call_count
  • arg_history_len
  • arg_histories_dropped

他にもある。。。が、私が使うのはcall_countくらいか。

戻り値を変更したい場合は、だいたいcustom_fakeを使っている。return_valやSET_RETURN_SEQ()もあるが、使い分けるのが面倒なので、自分で関数を作ってやってしまうことが多い。そういう関数はclassのstatic関数として作っている(こんな感じ)。gccだとnested functionsなるものに対応しているので、関数の中にそこで使うカスタム関数を定義しても良いそうだ。まあ、テスト用だから互換性とかそんなに気にしなくてもよいだろうしね。

call_countなんかは蓄積するので、クリアしたければResettingするとよかろう。書いてあるようにgtestの setUp()に仕込んで毎回クリアするといいんじゃないかね。


あとは、やりたいことがこれでできなかったらREADMEを読む、くらいか。

他のC言語用テストフレームワークを使ったことがないので比較はできないのだが、インストールもいらないし、valgrindと併用してメモリリークを見つけたりもできるし、私は気に入っている。

[c/c++]久々のfff

普段、C言語のことは「clang」と書いていたのだが、LLVMのclangもあったんだということを思い出した。
まあそんなことはどうでもよくて、久々にC言語を使っている。
あんだけ使っていたのに、しばらく使わなかっただけで忘れてしまっているところがあるのは哀しいのだが、まあ加齢もあるから仕方あるまい。

 

で、今日は単体テストについてだ。
自分がどうやってやっていたのか思い出そうとしたところ、これが出てきた。

hiro99ma blog: [c/c++]私のC言語 (5) - FFF
https://hiro99ma.blogspot.com/2016/08/ccc-5-fff.html

モックを作る meekrosoft/fff と(これはmicrosoftと名前をかけてるのかな)、その中にgtestが入っている。gtestは google testのことか、あるいはその前身だろう。ファイルのヘッダを見ると2005年なのだが、GitHubには残っていなさそうだったのだ。今のgoogle testのgtest.hもファイルヘッダは2005年なのだが、fff/gtest/gtest.hとファイルサイズが違いすぎる。おそらく複数のファイルを1つにまとめてあるのだろう。

 


今のfffはv1.1が最新だ。

Release Release v1.1 · meekrosoft/fff
https://github.com/meekrosoft/fff/releases/tag/v1.1

というわけで、私のサンプルも更新した。といってもfff.hだけだが。

hirokuma/fff_examples: FFFを使った例
https://github.com/hirokuma/fff_examples

前回が2016年なのもあるが、fff.hの差分が多すぎて違いを見るのが難しい。

なぜか分からんが、gccのオプションに-pthreadが必要になってしまった。gtest.hでpthread関数が使われているためのようなんだけど、今回gtest.hは変わってないんだよなぁ。。。

# define GTEST_HAS_PTHREAD (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_HPUX)

ああ、前回はcygwinで試したからこの条件に入っていなかったのか。納得だ。

 

テストを見ていて気付いたのだが、 weak_linkingサンプルはCソースしかない。
C++じゃないといけないと思い込んでいたのだが、いつしかC言語だけでテストが書けるようになっていたのか? でも、 __attribute__((weak)) とかMakefileに書いてあるし、なんか特殊なのかもしれん。

weakについてはこちらに書かれていた。

hiro99ma blog: [nrf51]app_error_handler()はWEAK
https://hiro99ma.blogspot.com/2015/02/nrf51apperrorhandlerweak.html

昔の私は言いました。

ライブラリ関数としてweakとしておけば、ユーザコードで上書きできるよ、みたいな感じらしい。

へー。
確かにMakefileを見ると、"FFF_GCC_FUNCTION_ATTRIBUTES"に"__attribute__((weak))"を設定していて、fff.hではFFF_GCC_FUNCTION_ATTRIBUTESを使ってFUNCNAMEを修飾している。
libfakes.aは、test内のDEFINE_FAKE_VALUE_FUNCみたいな関数をアーカイブしている。これらがweakになって置き換えられるようになっている、とういことか。
難しいこと考えるねぇ。

今までそういうのはcustom_fakeで行っていたと思う。
C++だとclassが使えて、そこにstaticな関数を作ることができるので、それをcustom_fakeにしてしまえば関数内で閉じたモックを作ることができるので便利だったのだ。
・・・と思っていたのだが、この例だと関数内に関数を書くことができてるな・・・そんなことできるんだっけ。nested functionをサポートしているならできるんだろう。

 

weakの件がC言語だけでテストコードまで書ける件と関係しているのかと思って見ていたのだが、関係はないのか?

別にC++でテストを作るのに困るわけでも無いし、どっちかといえば楽かもしれん。
ただ・・・テストコードは本体のCソースよりもだいたい量が多くなってしまう。そうするとGitHubの"Languages"でC++のプロジェクトのように見られかねないのだ。

image

まあいいんだけど、気になる場合は見直してみるのもよかろう。

 

うーん、今回は(も?)煮え切らない記事になってしまった。
次がんばろう。