Keep on moving

あんまりまとまってないことを書きますよ

Property-Based-TestingのShrinkingをkotlin testで学ぶ

こんにちは永遠のJVM初心者です。 Property-Based-TestingのShrinkingとはなんぞやがよくわかってなかったのでまとめます。

TL;DR

  • Property-Based-TestingにおけるShrinkingとは

Property-Based-TestingにおけるShrinkingとは

blog.johanneslink.net

jqwikの作者のブログを読んでる感じだと以下の点で重要とのこと

Unless you already have an inkling of what the issue might be, the number itself does not give you an obvious hint. Even the fact that it’s rather large might be a coincidence. At this point you will either add additional logging, introduce assertions or even start up the debugger to get more information about the problem at hand.

言ってしまえばpbtによる確認はrandomにgenerateされたものに過ぎないので、失敗した数値だけでてもよくわかんないよねー。
ロギングを追加したり、アサーションを導入したり、デバッガを起動しないと問題の原因はわからんよね。
なので「最も単純な」例をテスト結果で出してほしいよね。
この探索フェーズは、元のサンプルから開始し、それをだんだん小さくして再度propertyを確認しようとするので、Shrinking(縮小)と呼びます。

ということらしい。

Kotestでの例

こんな感じで超簡単な素数判定を考える。 これにPBTをかけると以下のような結果になる

gist.github.com

ここでは 592298304 でfailになったあと最小の数字までshrinkingしてくれているのがわかる。 最終的に4までShrinkingしてくれるおかげで、あぁなるほど4はそもそも素数じゃないなというのがわかってそもそもこのテスト自体が正しくないことが わかる。

KotestでのShrinkingの実装

現状でのInt型のShrinkerは以下のような実装らしい。

github.com

object IntShrinker : Shrinker<Int> {
   override fun shrink(value: Int): List<Int> =
      when (value) {
         0 -> emptyList()
         1, -1 -> listOf(0)
         else -> {
            val a = listOf(abs(value), value / 3, value / 2, value * 2 / 3)
            val b = (1..5).map { value - it }.reversed().filter { it > 0 }
            (a + b).distinct()
               .filterNot { it == value }
         }
      }
}

うーむコメントが少ないのでlistOfのルールがよくわかんない。 多分 value/3 がfailしたときのルールかな...(^見た感じだとfailしたら3でわって言っている様子が見えるかも) ちょっと自力で実装するにはもう少しコード読まないといけなそう

github.com

  • Returns the "next level" of shrinks for the given value, or empty list if a "base case" has been reached.
  • For example, to shrink an int k we may decide to return k/2 and k-1. とのことなので、なにかいい感じでルールを作るということらしい。ただ、そもそも順序関係が必要だよなぁ...

Shrinkingのルール変更

ShrinkingModeで設定可能

こんな感じ

        checkAll(PropTestConfig(shrinkingMode = ShrinkingMode.Bounded(50)),Arb.int(2..Int.MAX_VALUE)){
            i ->
            isPrimeNumber(i) shouldBe true
        }

やめたいときは ShrinkingMode.Offを指定すればいいらしい。

github.com

まとめ

PBTのツールではShrinkingが大事というのはよくドキュメントに書かれていたんだけど、実際に使ってみることで重要性がわかりました。 kotestでは今回の例でつかった intだけでなく long, string, listなどでも使用可能なのでbuild-inの方を組み合わせるだけでも十分テストでつかえそうです。 先人に感謝

KotestでProperty-Based-TestingするときのTips

こんにちは、Kotlin初心者です。

TL;DR

この記事は前日のこちらの記事の続きです。が、Property-Based-Testing知ってる方はそのまま読んでもらって問題ありません。

masahito.hatenablog.com

いろいろ書いてみた結果をまとめた

  • forAll() と checkAll()の使い分け
  • generationの回数を増やしたいor減らしたい
  • 失敗したときにもう少しテストを流したい
  • CustomGenerator

ひとことでいうと

github.com このドキュメント読むといいよ

forAll() と checkAll()の使い分け

ひとことで行ってしまうと関数の定義が違う

  • forAll : (a, ..., n) -> Boolean
  • check All : (a, ..., n) -> Unit

以上。例はこんな感じ

class PropertyExample: StringSpec({
    "String size" {
        forAll<String, String> { a, b ->
            (a + b).length == a.length + b.length
        }
    }

    "String size2" {
        checkAll<String, String> { a, b ->
            (a + b).length shouldBe  a.length + b.length
        }
    }
})

The checkAll approach will consider a test valid if no exceptions were thrown. なので、ちょっと考え方が違うとも言える。 自分はBooleanを返してほしい派なので大体forAllをつかっている。

generationの回数を増やしたいor減らしたい

標準では1000回生成するそう。増やしたいときには以下のようにする。

class PropertyExample: StringSpec({
   "some test" {
      checkAll<Double, Double>(iterations = 10000) { a, b ->
         // test here
      }
   }
})

失敗したときにもう少しテストを流したい

デフォルトでは失敗したら即打ち切りなんだけど、もっとテスト失敗パターンを出すことも可能。以下のようにする.

class PropertyExample: StringSpec({
   "some flakey test" {
      forAll<String, String>(PropTestConfig(maxFailure = 3)) { a,b ->
         // max of 3 inputs can fail
      }
   }
})

CustomGenerator

Generated values are provided by instances of the sealed class Gen. You can think of a Gen as kind of like an input stream but for property testing. Each Gen will provide a (usually) infinite stream of these values. Kotest has two types of generators - Arb for arbitrary (random) values and Exhaustive for a finite set of values in a closed space.

KotestではGenが基底クラスとして、2種類のジェネレータークラスが提供されている。

To write your own generator for a type T, you just create an instance of Arb or Exhaustive.

というわけで Arb<T> or Exhaustive<T>インスタンスを実装することで自分好みのGeneratorをつくれる

data class Yen(val amount: Long)
test("金額は5000円より高い") {
        val yenArb = arb { rs ->
            val nums = Arb.long(LongRange(5001, 10000)).values(rs)
            nums.map { num -> Yen(num.value) }
        }
        forAll<Yen>(yenArb) { yen1 ->
            yen1.amount > 5000L
        }
}

2変数以上のクラスをGenerateするためのTips

2変数くらいなら zip でOK.

        val userArb = arb { rs ->
            val names = Arb.string(minSize = 1).values(rs)
            val ages = Arb.int().values(rs)
            names.zip(ages).map { (name, age) ->
                User(
                    name.value,
                    age.value
                )
            }
        }

3変数以上なら Arb.bind() がおすすめ

        val orderArb: Arb<Order> =
            Arb.bind(
                Arb.long(0L),
                Arb.string(1, 24)
            ) { a, b -> Order(Item(a, b)) }

別解としてはflatmap+mapでもできたりします(for expression♫)

        val expiredCardArb: Arb<CreditCard> = arb { rs ->
            val nums = Arb.int(0, 9999).values(rs)
            val holders = Arb.string(minSize = 1).values(rs)
            val diffs = Arb.long(1, 24).values(rs)
            nums.flatMap { num ->
                diffs.flatMap { diff ->
                    holders.map { holder ->
                        val numStr = num.value.toString().padStart(4, '0')
                        CreditCard(
                            number = "${numStr}-${numStr}-${numStr}-${numStr}",
                            limit = today.minusMonths(diff.value),
                            holder = holder.value
                        )
                    }
                }
            }
        }

とはいえせっかくなので bindを使ったほうがよさそう。Sequenceで結果返してるのも実行効率考えると良さそう。

// 実装はこんな感じ
fun <A, B, T : Any> Arb.Companion.bind(genA: Gen<A>, genB: Gen<B>, bindFn: (A, B) -> T): Arb<T> = arb {
   val iterA = genA.generate(it).iterator()
   val iterB = genB.generate(it).iterator()
   generateSequence {
      val a = iterA.next()
      val b = iterB.next()
      bindFn(a.value, b.value)
   }
}

kotest/generators.md at master · kotest/kotest · GitHub

まとめ

いろいろ書いてみた結果をまとめた

  • forAll() と checkAll()の使い分け
  • generationの回数を増やしたいor減らしたい
  • 失敗したときにもう少しテストを流したい
  • CustomGenerator

ドキュメント読んでてscalaCheckなど先に出ているライブラリをちゃんと意識している印象です。 propertyをちゃんと見出すことができれば結構簡単に値をCustomeGenerator使えるのがいい感じです。 ぜひProperty-Based-Testing使ってみると良いと思います。 コードで変なところあったらぜひツッコミをください。

サンプルコード

github.com

Property-Based-Testingをkotlin testで学ぶ

こんにちは、Kotlin初心者です。Property-Based-Testingやってみようとおもったけど、あまり良くわかってなかったのでまとめてみました。

TL;DR

  • KotlinでProperty-Based-Testingやってみた
  • kotestでの流れ

サンプル

今回のサンプルはこちらにあります

github.com

Property-Based-Testing

そもそもProperty-Based-Testingとは

www.kzsuzuki.com

こちらのブログによるとこのブログを元ネタに説明してくれています。

jessitron.com

 記事では、Property-based testingの対照概念を、Example-based testingとしています。  Example-based testingとは、無数にある値から何がしかの基準で入力値を選択し、その入力値でコードを動かして、出力値と事後状態を確認する。つまり、普通に行われているテストですよね。

 Property-based testingでは、一つのテストで数百個の入力に対する結果を検証します。  ここで検証するのは、個々の入力に対するそれぞれの出力そのものではなく、(有効な)入力「群」に対する出力「群」のすべてが、statementで規定された性質(property)を満たしていることです。

jqwik作者の説明

Javaでの pbsツール jqwikの作者がpbsについてまとめてくれています。

jqwik.net

hypothesis.works

によるとFuzzingテストとpbsの違いは以下のような感じらしいです。

The main reason I’m drawing it here is that they do feel like they have a different character - property based testing requires you to reason about how your program should behave, while fuzzing can just be applied to arbitrary programs with minimal understanding of their behaviour - and also that fuzzing feels somehow more fundamental. [超訳] プロパティベースのテストでは、プログラムの動作を推論する必要がありますが、ファジングは、動作の理解が最小限の任意のプログラムに適用できます,そして、そのファジングは何かもっと根本的なもののようにに感じます。

ファジングテストはpbsとそんなにちがいはないのかもしれないというのがこの話で出てきています。実際に振る舞いをしっていてテストコードをつくるのが大事らしいですね。 プログラムの動作を推論というのは ^ の話でのstatement(property?)の話とほぼ同義の話をしているようにも思えます。

propertyを、「さまざまなデータポイントにおいて維持されるべき、ハイレベルな振る舞いの仕様」としています。

Property based testing is the construction of tests such that, when these tests are fuzzed, failures in the test reveal problems with the system under test that could not have been revealed by direct fuzzing of that system.

Kotlin testでは振る舞いを知っている上で、Example-based testingを補う概念として説明していますね。

GitHub - kotest/kotest: Powerful, elegant and flexible test framework for Kotlin

The problem is it's very easy to miss errors that occur when edge cases or inputs outside of what you considered are fed in. With property testing, hundreds or thousands of values are fed into the same test, and the values are randomly generated by your property test framework. [超訳] 問題は、エッジケースや想定外の入力が入力された場合に発生するエラーを見逃してしまうことです。プロパティテストでは、何百、何千もの値が同じテストに投入され、その値はプロパティテストフレームワークによってランダムに生成されます。

というわけで、これを使ってテストケースを考えてみます。

Kotlin Test

github.com

Kotlin と Kotlin Test(kotest)を使って実際のコードを書いてみます。

install

gradleでの例です。 Property-based-tesitingを使いたいときは kotestに追加で io.kotest:kotest-property-jvm を追加で入れる必要があります。

dependencies {
    testImplementation("io.kotest:kotest-runner-junit5-jvm:version") // for kotest framework
    testImplementation("io.kotest:kotest-assertions-core-jvm:version") // for kotest core jvm assertions
    testImplementation("io.kotest:kotest-property-jvm:version") // for kotest property test
}

こちらを参考に例を作ってみました。

テストの対象とするメソッドは以下のようなものです。

typealias Adder<T> = (T) -> T

object ListUtil {

    fun <T : Number> T.toAdder(): Adder<T> {
        return when (this) {
            is Long -> {
                { it -> (this as Long + it as Long) as T }
            }
            is Int -> {
                { it -> (this as Int + it as Int) as T }
            }
            is Double -> {
                { it -> (this as Double + it as Double) as T }
            }
            else -> throw AssertionError()
        }
    }

    fun <T : Number> sum(zero: T, list: List<T>): T {
        return list.map { it.toAdder() }.fold(zero) { acc, func -> func(acc) }
    }
}

テストコードは以下のようになります。 個々でのproperty(=「さまざまなデータポイントにおいて維持されるべき、ハイレベルな振る舞いの仕様」)は以下の2点です。 * "空のリストの合計は0" * "空ではないリストの合計はtailの合計にheadを足したもの"

class BusinessLogicTests : FunSpec({
    context("ListUtil.sum") {
        test("空のリストの合計は0") {
            ListUtil.sum(0, emptyList()).shouldBe(0)
        }
        test("空ではないリストの合計はtailの合計にheadを足したもの") {
            forAll<Int, List<Int>> { head, tail ->
                ListUtil.sum(0, listOf(head) + tail) == head + ListUtil.sum(0, tail)
            }
        }
    }
})

上の例では、forAllを使用することで、ランダムに生成されたIntとList[Int]の値を受け取ってテストを行っています。forAllに渡したテストの関数が(2, List()), (4, List(1, 4)のようなランダムな値で順番に呼び出されることでテストが実行されるイメージです。

まとめ

僕の理解だと以下のような流れで追加していくのが良さそうです。 1. サンプルベースのテスト 2. それを補完するためにPropertyをみつけ、Property-Based-Testingをする

propertyを、「さまざまなデータポイントにおいて維持されるべき、ハイレベルな振る舞いの仕様」としています。

もうすこし、複雑な例はまた明日書く予定です。 ビジネスロジックのテストに便利そうなので特定のdataclassを生成するときのTipsをまとめる予定です。

関連

github.com

gakuzzzz.github.io

taketoncheir.hatenablog.com

dev.classmethod.jp

attrsの使いどころとdataclass

masahito.hatenablog.com

この記事は^の記事の続きです。^を読んでから読むと良いと思います。 なお、今回のサンプルコードはこちらにあります。(間違いなどありましたらご指摘ください)

github.com

TL;DR

  • attrsのつかいどころ
    • 今Python3.5 or 3.6を使っている。今後dataclassを使いたい
    • dataclassで slotsなどを使いたい場合にボイラープレートコードを避けたい
  • slotsの効果

attrsとは

www.attrs.org 簡単にいうとPython でclassを書くときのボイラープレートになるところがスッキリかけるものです。 1 Overviewで書かれているコードを軽く解説します。

>>> import attr

>>> @attr.s
... class SomeClass(object):
...     a_number = attr.ib(default=42)
...     list_of_numbers = attr.ib(factory=list)
...
...     def hard_math(self, another_number):
...         return self.a_number + sum(self.list_of_numbers) * another_number


>>> sc = SomeClass(1, [1, 2, 3])
>>> sc
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])

>>> sc.hard_math(3)
19
>>> sc == SomeClass(1, [1, 2, 3])
True
>>> sc != SomeClass(2, [3, 2, 1]) # classレベルでの比較が可能です
True

>>> attr.asdict(sc) # class -> dict 変換
{'a_number': 1, 'list_of_numbers': [1, 2, 3]}

>>> SomeClass() # default値も使えます. factory=list の場合はempty listになります。
SomeClass(a_number=42, list_of_numbers=[])

>>> C = attr.make_class("C", ["a", "b"]) # methodでクラスがつくれちゃいます。
>>> C("foo", "bar")
C(a='foo', b='bar')

attrsこぼれ話

じつはこのライブラリ Python 3.7 で入った dataclass のもとになった実装だったりします。 詳しくはpepを確認してください。 www.python.org

ちなみに dataclass入れるよりattrsをPythonで標準でいれちゃえば? という声もあったみたいですがそれはやめたみたいです。(guidoがstdlibに入れるのに反対している、この辺を参照。)

github.com 「attrsはいいもんだが、これがファイナルアンサーじゃないしいいところだけ取り出そうぜ」という感じっぽい。

attrsのdataclassとの使い分け

attrsを使うべき

  • 今Python3.5 or 3.6を使っている. 2
    • 特に現在namedtupleを使っているが、3.7にupgradeするタイミングでdataclassの使用を考えている
  • dataclassで slotsなどをつかいたい(for Python3.7以降)
@dataclasses.dataclass
class B:
   __slots__ = ('b',) # __slots__ 追加
   b: int

例えばこんな感じでdataclassでも __slots__ が使えることを前回紹介した。が、 __slots__ に設定するところとプロパティを両方書かないといけないため 実行時にエラーになることが結構おおい。

これをattrsをつかうと以下のようにかける

# slots=Trueをつけるとslotsが有効に
@attr.s(auto_attribs=True, slots=True)
class AttrsMessage:
    sender: str
    recipient: str
    body: str

個人的にはdataclassを使いたくなったら無条件でattrsを使うのをおすすめしたいと思っています。 ただプロジェクトの考え方でなるべく使うライブラリを増やしたくない場合はdataclassでも問題はないかもしれません。 が、複数人で開発するときではミスをするばあいが多いため入れておいたほうが便利だと思います。

余談 __slots__ の効用

Pympler を使ってPython3.8での __slots__ を使ったとき使わないときのメモリ使用量を比べてみました。 使い方についてはこちらが詳しいです。 Asizeof モジュールの使い方 | Xoxzoの公式ブログ

# use NamedTuple
Message = NamedTuple("Message", [("sender", str), ("recipient", str), ("body", str)])

if sys.version_info.minor >=7:
    import dataclasses
    @dataclasses.dataclass
    class DataClassMessage:
        sender: str
        recipient: str
        body: str


    @dataclasses.dataclass
    class SlotMessage:
        __slots__ = ["sender", "recipient", "body"]
        sender: str
        recipient: str
        body: str


@attr.s(auto_attribs=True, slots=True, weakref_slot=False)
class AttrsMessage:
    sender: str
    recipient: str
    body: str

if __name__ == "__main__":
    pos = Message(
        sender="sender@exmaple.com",
        recipient="recipient.example.com",
        body="Hello, World!",
    )
    if sys.version_info.minor >=7:
        simple = DataClassMessage(
            sender="sender@exmaple.com",
            recipient="recipient.example.com",
            body="Hello, World!",
        )
        slotted = SlotMessage(
            sender="sender@exmaple.com",
            recipient="recipient.example.com",
            body="Hello, World!",
        )
        print(
            "Dataclass %d, Slotted dataclass %d"
            % asizeof.asizesof(simple, slotted)
        )
    attrs = AttrsMessage(
        sender="sender@exmaple.com",
        recipient="recipient.example.com",
        body="Hello, World!",
    )
Dataclass 536, Slotted dataclass 56
NamedTuple 272,  Attrs 56

こんな感じで3propertiesのclassでも __slots__ のありなしで 10倍程度メモリ使用量が違ったりします。 特に実行回数が多い関数の返り値などでdataclassを使用する場合は使用メモリのためにも使っておくことが無難です。

まとめ

本記事では以下の説明をしました。

  • attrsのつかいどころ
    • 今Python3.5 or 3.6を使っている。今後dataclassを使いたい
    • dataclassで __slots__ などを使いたい場合にボイラープレートコードを避けたい
  • __slots__ の効果

  1. Scalaのcase class, Kotlinのdata class的なものだと思ってもらうと良いと思います(プロパティはデフォルトではimmutableではないですが)

  2. https://devguide.python.org/#status-of-python-branches Python3.5/3.6もまだEOLでない

dataclassと __slots__ について調べた

対象バージョン Python 3.7以降

TL;DR

  • dataclassでも __slots__ を使える
  • __slots__ をつかうとpropertyの追加ができなくなる(自由度が下るが、コードをおいやすくなる)

slots とは

docs.python.org

__slots__ を使うと、(プロパティのように) データメンバを明示的に宣言し、 (明示的に __slots__ で宣言しているか親クラスに存在しているかでない限り) __dict____weakref__ を作成しないようにできます。 __dict__ を使うのに比べて、節約できるメモリ空間はかなり大きいです。 属性探索のスピードもかなり向上できます。

ということらしい。

dataclassの動作

dataclassを自分で使うときは私の用途だと関数の返り値/引数とかで使うときがおおいのでメモリ空間の節約できると便利だと思ってしらべてみました。 また、データ型の表現で使うときはプロパティの追加ができないほうが便利かなーと。

>>> import dataclasses
>>> @dataclasses.dataclass
... class A:
...    b: int
>>> a = A(1)
>>> a.b
1
>>> a.c =2 # propertyを追加できる
>>> a.c
2
>>> dir(a) # __dict__ や __weakref__ が作成される
['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'b', 'c']

というわけでdataclassでは __slots__ が有効ではないみたい。 dataclassの動作を見てる感じだと、__slots__ を有効にするオプションはないみたい docs.python.org

dataclss with __slots__

www.python.org PEP-557では自動的に追加することは一旦やめる的なことが書かれている。 実際に使うにはこんならしい。

>>> @dataclasses.dataclass
... class B:
...   __slots__ = ('b',) # __slots__ 追加
...   b: int
... 
>>> b = B(2)
>>> dir(b)
['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'b']
>>> b.d =3 # propetyの追加不可
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'B' object has no attribute 'd'

まとめ

ということで dataclassをつかったクラスでも __slots__ を定義すれば設定できる。ただ、これ何回も書くのめんどいので なんかいも設定するならば attrs 使ったほうがよさそう。明日はattrsを使ったパターンを書こうと思う

Ubuntu 19.10でSony WF-1000XM3 を利用する

Ubunto19.10でSony WF-1000XM3 をつかおうとしたらすぐに使えなかったのでメモを残します。

2020/06/22 追記: Ubuntu20.04でも同様に利用可能なことを確認済み

TL;DR

  • pulseaudio-modules-bt のインストールが必要。公式ではないのでppaなどでいれる。
  • 音量コントロールをイヤホン側でできるようにすると便利

Ubuntu19.10から使えるようになるまで

Ubuntu19.10 ではBluetoothでの接続はできるのですが、なぜか音声の出力で選択できません。というわけで 以下のインストールが必要です。

必要なdebファイルをインストールする。以下のgithub issueに書かれているものを入れること。

github.com

試して見た感じだと pulseaudio-modules-bt がインストールできていれば 無事Ubuntuから使える様子

https://f.bytefuse.net/pulseaudio-modules-bt-ppa/

音量の調整

どうやらUbuntuだけでつかっているとイヤホン本体の音量は調整できない様子。 公式のサポートにならってiOS/Androidのアプリから設定する or L側のタッチセンサー機能を[外音コントロール]から[音量コントロール]へ変更するのがいいみたい。 knowledge.support.sony.jp

私はL側のタッチセンサー機能を[外音コントロール]から[音量コントロール]へ変更するほうにしました。

まとめ

ちょっとひと手間必要でしたが、無事Ubuntu19.10 からつかえるようになりました。 イヤホンを長時間つけ続けるのが苦手なんですが、こちらをつかうことで会議等で長い時間つけ続けるのも楽になりそうです。 ビデオ会議は音声(マイク、スピーカー)で疲れが変わるのでいいものを使う価値は大きいと思います。

先人の皆様に感謝

参考

github.com

https://www.labohyt.net/blog/server/post-4563/

Amazon WorkspacesをUbuntu 19.10で動かす

Amazon WorkSpacesのクライアントをUbuntu 19.10で動かせたのでメモを残します。

TL;DR

  • ArchのAURは探すとなんでもある
  • debファイルのインストール先が探せればうごかせる(ちゃんと動くかは別ですが。。。

Amazon Workspacesとは

Amazon WorkSpaces というマネージド型のデスクトップ (DaaS) ソリューションがあります。 詳しくはこちらを。 aws.amazon.com

公式クライアント

Windows, Macでは公式クライアントが存在します。ありがたいことにLinuxでもうごくとのこと。 しかし、今日現在だとUbuntu 18.04にしか対応してないです。

clients.amazonworkspaces.com

ただ残念ながら最新版の19.10には対応してない。 残念ながらこのぺーじだとaptのレポジトリ経由しかインストール方法が書かれていません。 困ったなーと思ったら友達がおしえてくれました。

ArchのAURレポジトリ

ArchとかAURってなんなのかはここでは省きます。 ちなみにこちらです。

aur.archlinux.org

ここをみると debファイルのダウンロードができます。 ちなみに これをふつうにdebコマンドでインストールできました。

$ sudo dpkg -i workspacesclient_amd64.deb

私はubuntu(gtk)を使ってるのでそのまま動きましたが、gtkを入れてない方はインストールが必要そうです。 ちゃんと試してないですが動かすにはいかが必要そうです。

  • gtk3
  • webkit2gtk
  • libsoup

ひとまず動作するところまでは確認できました。たまにマウスの動きが怪しくなるんですが、いまのところ動いています。

先人に感謝

余談

Archでも以下のような感じでそのまま展開してるだけっぽいです。

package() {
    cd "$srcdir"
    tar axfv data.tar.xz -C "${pkgdir}"
}