- 投稿日:2019-04-30T23:00:33+09:00
7言語に同時に入門する。
入門するのは以下の言語!
- Nim
- Elixir
- Go
- Lua
- Python
- Scala
- Dart
今回は環境構築してHello Worldするところまでです。
では早速環境構築していきます。
ちなみに、今回インストールするマシンはWindows機(Surface)となっております。
環境構築編
Nim
https://trap.jp/post/452/
ここを参考にしつつ進めていきます。Nimのインストールページ(windows用)
https://nim-lang.org/install_windows.html
NimはC言語に一回変換してから実行形式にするトランスパイラ的なやつなのでC言語のコンパイラが必要です。
公式ページでMingWというのがおすすめされてたので今回はこれを入れます。
上記のページの下の方にNotes about compiler dependenciesという部分があるのでそこにあるリンクからDLしました。7zipとかで解凍して出てきたファイルを今回はC:devに入れます。
そして環境変数に追加。環境変数はコントロール パネル>システムとセキュリティ>システム>システムの詳細設定>環境変数でいじれます。
システム環境変数のPathのところに追加します。
今解凍したMingWの中のbinの階層をしていします。今回だとC:\dev\mingw64\binになります。そのあとPowerShellで
gcc -vで動作確認します。
なんか出てきたらOKです(だと適当過ぎるので一番最後にバージョンが表示されていること程度は確認します)。さて、本命のNim自体のインストールです。
Mac、Linuxだとchoosenimというのが使えるらしいです。
一応Windowsでも使えそうだったけれど、公式ページでUnixの方でしか紹介されていなかったのでやめておきました(マイナーな言語でマイナーなバグを踏んではかなわないので)。
バージョン管理できないので辛さはありますが、まぁ所詮趣味なので良いでしょう。https://nim-lang.org/install_windows.html
NimのダウンロードページからDLしてきます。
ダウンロードしてきたら解凍します。解凍したファイルを今回はC:\devに入れます。
ついでになんとなく解答したフォルダからバージョン名を消して単なるnimにしました(ようはC:\dev\nimの中にbinとかfinish.exeが入っている状態)。でnimくんも環境変数に追加します。
今回もシステム環境変数のPathにnimのbinを追加します(今回の例ではC:\dev\nim\bin)。
で、PowerShellで
nim --versionで動作確認します。一行目にバージョンが出てくるはずです。
無事に出てきたらnimの環境構築はおしまいです。お疲れ様でした。ちなみになんですが、finish.exeを実行して適当に環境変数を設定してもらおうと思ったらうまくできなくて手動で設定しました。
Elixir
Elixirくんもインストーラーから
https://elixir-lang.org/install.htmlと、思わせつつ、Chocolatyが使えるのでこっちでインストールします。
ChocolatyというのはUbuntuとかのapt-get的な存在です。つまりアップデートがコマンドでできる!
ちなみにnimでもChocolatyが使えはするんですけど、nimの最新バージョンが0.19.4に対してChocolatyで入れようとするとバージョンが0.11.2だったのでやめました。アップデートプリーズ(やり方がわからなくてできない)。
Elixirは最新が1.8.1に対してChocolatyで入れたときのバージョンが1.8.0で微妙に遅れてはいますが、まぁ許容範囲かな、って感じです。ElixirのインストールページでもChocolatyが載っているのでそのうち更新されるのでは、と思っています。いや、インストーラーからやるわって人はインストーラーをDLしてNextを押し続ければインストールされるのではないかな。公式に
Click next, next, …, finish
って書いてあるし。
さて、Chocolatyを使うにはChocolatyをインストールしないといけないわけですが、今回のマシンには諸事情によりすでにインストールしていたため、詳細なやり方をかけません。
一応参考URLをいくつか載せておくのでそちらを参考にどうぞ……。
https://chocolatey.org/docs/installation
https://qiita.com/konta220/items/95b40b4647a737cb51aaさて、今回はすでにインストールされているChocolatyを使うので念の為Chocolaty自体のアップデートを確認しておきます。
choco upgrade chocolatey Chocolatey v0.10.11 Upgrading the following packages: chocolatey By upgrading you accept licenses for the packages. You have chocolatey v0.10.11 installed. Version 0.10.13 is available based on your source(s). Progress: Downloading chocolatey 0.10.13... 100% chocolatey v0.10.13 [Approved] chocolatey package files upgrade completed. Performing other installation steps. The package chocolatey wants to run 'chocolateyInstall.ps1'. Note: If you don't run this script, the installation will fail. Note: To confirm automatically next time, use '-y' or consider: choco feature enable -n allowGlobalConfirmation Do you want to run the script?([Y]es/[N]o/[P]rint):どうやらアップデートがあったみたいなのでyでアップデートします。
そのあとPowerShellを再起動しろ、みたいなメッセージが流れるので一回閉じてもう一度PowerShellを起動します。
そしたら以下のコマンドを実行します。
cinst elixir Chocolatey v0.10.13 Installing the following packages: elixir By installing you accept licenses for the packages. Progress: Downloading erlang 21.2... 100% Progress: Downloading Elixir 1.8.0... 100% erlang v21.2 [Approved] erlang package files install completed. Performing other installation steps. The package erlang wants to run 'chocolateyInstall.ps1'. Note: If you don't run this script, the installation will fail. Note: To confirm automatically next time, use '-y' or consider: choco feature enable -n allowGlobalConfirmation Do you want to run the script?([Y]es/[N]o/[P]rint):こんな感じで出てくるのでyを押してエンターします。
あと、これを見る感じこのコマンドだけでElixirだけではなく、Erlangもインストールしてくれるみたいですね。
ElixirはErlangという言語の仮想マシン上で動くのでErlangのインストールも必要となっております。JavaにおけるScalaみたいなものです。Erlangのインストールが終わったらElixirのインストールについても聞かれます。
Elixir v1.8.0 [Approved] elixir package files install completed. Performing other installation steps. The package Elixir wants to run 'chocolateyInstall.ps1'. Note: If you don't run this script, the installation will fail. Note: To confirm automatically next time, use '-y' or consider: choco feature enable -n allowGlobalConfirmation Do you want to run the script?([Y]es/[N]o/[P]rint):yを押してインストールしていきます。
一応PowerShellを閉じてから開き直してElixirの動作確認をします。
elixir --versionでバージョン確認をします。
一行目にErlangのバージョンが表示されて次にElixirのバージョンが表示されました。
これにてElixirの環境構築は終了です。Go
一応GoのDLページ
https://golang.org/dl/ここを見つつ進めます。
https://qiita.com/yoskeoka/items/0dcc62a07bf5eb48dc4bどうもChocolateyが使えそうなので一応最新バージョンとChocolateyの方のバージョンを確認します。
最新は1.12.1に対してChocolateyが1.12で良い感じなのでChocolateyを使ってインストールしていきます。choco install golangこれを打つといつもの通り
Chocolatey v0.10.13 Installing the following packages: golang By installing you accept licenses for the packages. Progress: Downloading golang 1.12... 100% golang v1.12 [Approved] golang package files install completed. Performing other installation steps. The package golang wants to run 'chocolateyinstall.ps1'. Note: If you don't run this script, the installation will fail. Note: To confirm automatically next time, use '-y' or consider: choco feature enable -n allowGlobalConfirmation Do you want to run the script?([Y]es/[N]o/[P]rint):こんな感じで聞かれるのでいつも通りyを入力します。
終わったらPowerShellを閉じて開き直します。そしたら
go versionでバージョン確認をします。
シンプルに一行でバージョンが表示されました。
バージョンが表示されたのでこれでGoの環境構築はおしまいです。ちなみに、参考に貼ったQiitaの記事では環境変数を設定していますが、私の場合確認したら環境変数が設定されていました。
Lua
ここを参考にしつつ進めます。
http://yohshiy.blog.fc2.com/blog-entry-291.htmlてきとうにzipを落としてきます。
私は何も考えずに1つ目のDLリンク(http://luabinaries.sourceforge.net/download.html)から落としてきました。
参考にしたページでは1つ目の方には最新版がない、となっていましたが、私が見に行ったときには最新版(5.3.5)があったのでとりあえずはどっちでもいいと思います。
さて、落としてきたZipを解凍したところ以下のファイルが出てきました。
lua53.dll
lua53.exe
luac53.exe
wlua53.exe
参考にしているページではwlua53.exeについて書かれていないのでおとなしく同じ方でDLすれば良かったと少し後悔していますが、必要なファイルはあるっぽいので気にせず行きます。
どうも、解凍したところに環境変数を設定するみたいなので、解答したファイルを移しておきます。
C:\dev\lua
の中に上記の4ファイルがある状態にしました。
ここを環境変数に追加します。これまでと同じようにシステム環境変数のpathに追加していきます。
追加したら
lua -v
でバージョンを確認します。1つ目のDLリンクから落としてきたらうまく動かなかったので参考にしたページ通り2つ目のDLリンクから落としてきます。
どうも、解凍したところに環境変数を設定するみたいなので、解凍したファイルを移しておきます。
C:\dev\lua
この中に3ファイルがある状態にしました。
ここを環境変数に追加します。これまでと同じようにシステム環境変数のpathに追加していきます。
追加したらlua -vでバージョンを確認します。
無事に出てきたのでこれでLuaの環境構築は終了です。Python
Chocolateyくんが使える(最新3.7.2でChocolateyも3.7.2だった)ので使っていきます。
すこし調べた感じAnacondaが一式揃っていて便利な感じもありましたが、機械学習が主目的ではないので普通に入れます。
いまいち理解しきってないですが、Pythonはデフォルトでvenvというコマンドが使えて仮想環境が作れるらしいです。要はデフォルトでバージョン管理ができるようなものなのだろうか。ともかくChocolateyでPythonを入れていきます。
choco install python Chocolatey v0.10.13 Installing the following packages: python By installing you accept licenses for the packages. Progress: Downloading python3 3.7.2... 100% Progress: Downloading chocolatey-core.extension 1.3.3... 100% Progress: Downloading python 3.7.2... 100% chocolatey-core.extension v1.3.3 [Approved] chocolatey-core.extension package files install completed. Performing other installation steps. Installed/updated chocolatey-core extensions. The install of chocolatey-core.extension was successful. Software installed to 'C:\ProgramData\chocolatey\extensions\chocolatey-core' python3 v3.7.2 [Approved] python3 package files install completed. Performing other installation steps. The package python3 wants to run 'chocolateyInstall.ps1'. Note: If you don't run this script, the installation will fail. Note: To confirm automatically next time, use '-y' or consider: choco feature enable -n allowGlobalConfirmation Do you want to run the script?([Y]es/[N]o/[P]rint):いつもどおりこんな感じで聞かれるのでyを押します。
で、pipも一緒に入れます。
pipはパッケージ管理ができるやつらしいです。どうせなので一緒に入れます。choco install pip Chocolatey v0.10.13 Installing the following packages: pip By installing you accept licenses for the packages. Progress: Downloading easy.install 0.6.11.4... 100% Progress: Downloading pip 1.2.0... 100% easy.install v0.6.11.4 [Approved] easy.install package files install completed. Performing other installation steps. The package easy.install wants to run 'chocolateyInstall.ps1'. Note: If you don't run this script, the installation will fail. Note: To confirm automatically next time, use '-y' or consider: choco feature enable -n allowGlobalConfirmation Do you want to run the script?([Y]es/[N]o/[P]rint):こちらもいつもどおりyを押していきます。
そのあとなんか途中でエラーが出た気がしますがyを押していきます。
多分pythonがインストールされてないよ的なことを言われたのでおそらくPythonを入れたあとにPowerShellを開き直さないでやったのが悪いのだと思います。PowerShellを開き直してPythonのバージョンを確認します。
python --versionでバージョンが出てきたのでpythonのインストール自体は大丈夫そうです。
pipの確認もします。pip --versionこちらもバージョンが出てきたので無事pipの方も大丈夫そうです。
というわけでこれにてPython完了です。
Scala
次はScalaです。
と、言いたいところですが、すでにこのPCには構築してしまっていたので参考リンクだけ貼ります。
https://dwango.github.io/scala_text/sbt-install.html
ScalaのバージョンとJDKのバージョンには注意しましょう。私はこれで少しハマりました。
ちなみに、Chocolateyがすでに入っていた諸事情はこれです。
Dart
最後にDartです。
これもChocolateyが使える(最新2.2.0でChocolateyも2.2.0)ので使っていきます。
というか公式のインストールページ( https://www.dartlang.org/tools/sdk )にChocolateyの事書いてありますね。
といことでインストールしていきます。
choco install dart-sdk後ろに--preをつけるとdev release(要は開発中の不安定版)を入れられるみたいですが、今回は安定版を入れます。
このコマンドを入れるとChocolatey v0.10.13 Installing the following packages: dart-sdk By installing you accept licenses for the packages. Progress: Downloading dart-sdk 2.2.0... 100% dart-sdk v2.2.0 [Approved] dart-sdk package files install completed. Performing other installation steps. The package dart-sdk wants to run 'chocolateyInstall.ps1'. Note: If you don't run this script, the installation will fail. Note: To confirm automatically next time, use '-y' or consider: choco feature enable -n allowGlobalConfirmation Do you want to run the script?([Y]es/[N]o/[P]rint):いつものyを入力するやつが出てくるのでyを入力します。
終わったらPowerShellを開き直してバージョン確認します。
dart --versionこれでバージョン確認ができたのでDartの環境構築は終了です。
お疲れ様でした。Hello World編
無事環境構築が終わったので各言語でHello Worldしていきます。
Nim
ではNimに入門していきましょう。
https://qiita.com/nunulk/items/e3679ce256b8071e5255適当な階層でhelloWorld.nimファイルを作ります
そして中身にhelloWorld.nimecho "Hello World"と書きます。
コンパイルは
nim c -r helloWorld.nimこんな感じです。
これでhelloWorld.nimと同じ階層にhelloWorld.exeができます。
-rで自動的に実行までしてくれるのでHintがいろいろでたあとにHello Worldが出力されます。
ちなみに一回目のコンパイルはそこそこ時間がかかりますが、二回目以降はキャッシュされるため、短くなります。確か二回目以降は差分だけがコンパイルされると聞いた記憶があります。Elixir
Elixirに入門します。
https://elixir-lang.jp/getting-started/introduction.htmlhelloWorld.exs
というファイルを作ります。中身は
helloWorld.exsIO.puts "Hello world from Elixir"で
elixir simple.exs実行できます。
コンパイルして実行したかったのですが、コンパイルするにはちゃんとプロジェクトを作らないとできないっぽかったのでここでは一旦諦めます。
Go
Goに入門していきます。
https://golang.org/doc/install公式にご丁寧にHelloWorldが書いてあったのでコピペしました。
適当なところにhelloWorld.goファイルを作って以下をコピペ。helloWorld.gopackage main import "fmt" func main() { fmt.Printf("hello, world\n") }コンパイルは
helloWorld.goの階層で
go buildでコンパイルできました。
これ、ファイルを指定していないから、同じ階層にmainを持つファイルが2つあったらどうなるんだろうか……。
コンパイルしたらHelloWorld.exeができるので実行するとHelloWorldがでてきます。ちなみに
go run helloWorld.goみたいに書くとビルドして実行までしてくれます。
Lua
Luaに入門しましょう。
https://dotinstall.com/lessons/basic_lua適当なところにhelloWorld.luaを作ります。
中に
helloWorld.luaprint("Hello World")を書いて
コマンドで
lua helloWorld.luaで実行してくれます。
スクリプト言語なのでコンパイルは特にないです。Python
Pythonに入門します。
https://www.python-izm.com/introduction/execution/今回入れたのはPython3系なのでhelloWorld.pyというファイルを作って以下のように入れます。
helloWorld.pyprint('Hello World')そしたら次のコマンドで実行します。
py helloWorld.pyうまく行かなかったら
C:\Python37\Scriptsみたいなところに環境変数を通したりすると動くような気がします。
私はVSCodeのターミナルでやっていて、最初うまく動かなかったんですけど、lintがどうのこうの言われて入れたら動いたのでそんな感じです(適当で申し訳ない)。Scala
Scalaに入門していきます
https://dwango.github.io/scala_text/sbt-compile-execute.htmlここを見る感じプログラム本体だけではなく、設定ファイル的なものも必要っぽいので一緒に作ります。
適当に階層を切って
helloWorld.scala build.sbtを作ります。
それぞれの中身はhelloWorld.scalaobject HelloWorld { def main(args: Array[String]): Unit = { println("Hello World") } }build.sbtscalaVersion := "2.12.8" scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint")こんな感じで作ったら
sbtを起動させてrunさせるとコードが実行されます。
sbt (sbtが起動し終わるのをまつ) run (コンパイルを待つ) Hello Worldみたいな感じです。()のところにはログが出ます。ログが出てる間にいろいろファイルとかフォルダが生成されます。
sbtの起動が長いのは一回起動させたら起動させっぱなしで毎回ではないので問題ないと思います。
コンパイルも2回目以降は早いので最初だけ待ちます。Dart
最後にDartに入門します。
https://www.dartlang.org/samples
適当にhelloWorld.dartファイルを作って中身を以下のようにします。
helloWorld.dartvoid main() { print('Hello World'); }あとは実行しておしまいです。
dart helloWorld.dartスクリプト言語なのでコンパイルとかなくそのまま実行されて結果が表示されます。
終わり
同時に7言語もやると環境構築はともかく、HelloWorldだけでも時間がかかるので他の人はやめておこうね。
次回はFizzBuzzの予定。
他にも良い題材があれば教えてもらえるとありがたいです。
- 投稿日:2019-04-30T21:01:21+09:00
bitbucket-pipelines テストサマリを表示
概要
bitbucket pipelinesではjunitの形式のxmlからテストのサマリを表示することができる。
pipelinesで取得できたサマリーはpipelinesのページに表示される
- bitbucket pipelinesでテストサマリを表示する
- 参考
環境
手順
- bitbucket pipelinesを使える状態にする
参考
bitbucket pipelines セットアップはこちら
- golangでjunitの形式でテストのレポートを作成する場合は
go-junit-reportを使用するのが便利$ go get -u github.com/jstemmer/go-junit-report
- テスト時に特定のディレクトリにレポートを出力することでpipelinesで表示してくれるようになる。以下がpipelinesファイルの例
bitbucket-pipelines.yml# This is a sample build configuration for Go. # Check our guides at https://confluence.atlassian.com/x/5Q4SMw for more examples. # Only use spaces to indent your .yml configuration. # ----- # You can specify a custom docker image from Docker Hub as your build environment. image: golang:1.7 pipelines: default: - step: script: # Modify the commands below to build your repository. - PACKAGE_PATH="${GOPATH}/src/bitbucket.org/${BITBUCKET_REPO_FULL_NAME}" - mkdir -pv "${PACKAGE_PATH}" - tar -cO --exclude-vcs --exclude=bitbucket-pipelines.yml . | tar -xv -C "${PACKAGE_PATH}" - echo "${GOPATH}" - cd "${PACKAGE_PATH}" - go get -v - go build -v - go get -u github.com/jstemmer/go-junit-report - mkdir -p "/opt/atlassian/pipelines/agent/build/test-reports/" - go test -v 2>&1 | go-junit-report -set-exit-code=1 > /opt/atlassian/pipelines/agent/build/test-reports/junit.xml
mkdir -p "/opt/atlassian/pipelines/agent/build/test-reports/"で出力先のディレクトリを作成go test -v 2>&1 | go-junit-report -set-exit-code=1 > /opt/atlassian/pipelines/agent/build/test-reports/junit.xmlでjunit形式のレポートを作成したディレクトリに出力する-set-exit-code=1をgo-junit-reportの実行オプションに指定することで、go testの実行でFAILがあったときに、中断することができる- 上記のpipelinesファイルでpipelineが実行された場合、テストの失敗時にビルドが失敗する。(成功時にもレポートに失敗した情報が入っている場合はサマリが表示されるが、コマンドが成功した場合は後段のコマンドが実行されてしまう。)
- 失敗したテストケースがない場合はサマリは表示されない
成功時
- 失敗時にはタブにTestsが追加されており、サマリが表示される
失敗時
- 投稿日:2019-04-30T19:03:40+09:00
A Tour of Go(Exercise: Web Crawler)をツアーで学習したことだけで解答
目的
A Tour of Goは、Goを始める人なら誰でも通る道かと思うが、Exerciseは初見では結構頭を捻った。
仮に解けなくても、多くの解答がWebにあるので、理解して進めることができる。
ただし、最後の問題だけは、探してもツアーの中で学んだものだけで解かれたものがなかった。# 探し方が悪いだけかもしれないが。。。
今回、ツアーで学んだものだけで解いたので、(スマートではないだろうが)残しておく。問題内容
A Tour of Go | Exercise: Web Crawler
同じURLを2度取ってくることなく並列してURLを取ってくるように、 Crawl 関数を修正してみてください(注1)。
補足: 工夫すれば Crawl 関数のみの修正で実装できますが、無理に Crawl 関数内部に収める必要はありません。
ひとこと: mapにフェッチしたURLのキャッシュを保持できますが、mapだけでは並行実行時の安全性はありません!
問題となるコード
exercise-web-crawler.gopackage main import ( "fmt" ) type Fetcher interface { // Fetch returns the body of URL and // a slice of URLs found on that page. Fetch(url string) (body string, urls []string, err error) } // Crawl uses fetcher to recursively crawl // pages starting with url, to a maximum of depth. func Crawl(url string, depth int, fetcher Fetcher) { // TODO: Fetch URLs in parallel. // TODO: Don't fetch the same URL twice. // This implementation doesn't do either: if depth <= 0 { return } body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) for _, u := range urls { Crawl(u, depth-1, fetcher) } return } func main() { Crawl("https://golang.org/", 4, fetcher) } // fakeFetcher is Fetcher that returns canned results. type fakeFetcher map[string]*fakeResult type fakeResult struct { body string urls []string } func (f fakeFetcher) Fetch(url string) (string, []string, error) { if res, ok := f[url]; ok { return res.body, res.urls, nil } return "", nil, fmt.Errorf("not found: %s", url) } // fetcher is a populated fakeFetcher. var fetcher = fakeFetcher{ "https://golang.org/": &fakeResult{ "The Go Programming Language", []string{ "https://golang.org/pkg/", "https://golang.org/cmd/", }, }, "https://golang.org/pkg/": &fakeResult{ "Packages", []string{ "https://golang.org/", "https://golang.org/cmd/", "https://golang.org/pkg/fmt/", "https://golang.org/pkg/os/", }, }, "https://golang.org/pkg/fmt/": &fakeResult{ "Package fmt", []string{ "https://golang.org/", "https://golang.org/pkg/", }, }, "https://golang.org/pkg/os/": &fakeResult{ "Package os", []string{ "https://golang.org/", "https://golang.org/pkg/", }, }, }このコードは何をやっているのか
まず
mail()では、Crawl("https://golang.org/", 4, fetcher)を呼んでいる。
また、fetcher.Fetch(url)で取得した複数のURLに対して、Crawl(u, depth-1, fetcher)を呼んでいることがわかる。
Crawl()関数を見てみると、depth <= 0でreturnしており、Crawl()関数を呼ぶ度にdepth-1している。
つまり、各URLに対して最大で3回Crawl()関数が呼ばれてる。次に、
fetcherの形と値を照らし合わせて見てみる。39-44Ltype fakeFetcher map[string]*fakeResult type fakeResult struct { body string urls []string }54-61Lvar fetcher = fakeFetcher{ "https://golang.org/": &fakeResult{ "The Go Programming Language", []string{ "https://golang.org/pkg/", "https://golang.org/cmd/", }, },1つ目を見ると
fetcherは、map[key]{ body, urls[XXX, YYY] }の形をしてることがわかる。
fetcher["https://golang.org/"].bodyを呼ぶと、The Go Programming Language
fetcher["https://golang.org/"].urlsを呼ぶと、[https://golang.org/pkg/ https://golang.org/cmd/]
が出力される。
ここまで分かると、fetcher.Fetch(url)はつまるところ、上記を返しているに過ぎないことがわかる。そして、
urlsの各々に対して、同じことを最大3回、もしくはmapのkey(url)が存在しない(=not found)と分かるまで繰り返している。解答までの道のり
問題内容から、やることとしては大きく2つ
- URL取得の並列化
- URLを1度しか呼ばない
1.に関しては、
goroutineを使えば可能。また、同期を取るためにchanelを使えばよさそう。
2.に関しては、ひとことに書いてあるとおり、mapを使えばできそう。
ただし、並列実行するので排他制御mutex(Lock(),Unlock())する必要がありそう。まずは
main()で呼ばれているCrawl()を見てみる。func Crawl(url string, depth int, fetcher Fetcher) { // TODO: Fetch URLs in parallel. // TODO: Don't fetch the same URL twice. // This implementation doesn't do either: if depth <= 0 { return } body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) for _, u := range urls { Crawl(u, depth-1, fetcher) } return }再帰呼び出しとなっているが、
chanelを使うとなると、もう1つ関数を中で作ればよさそう。元の処理をまるっと
crawlChild()に入れてみた。func Crawl(url string, depth int, fetcher Fetcher) { // TODO: Fetch URLs in parallel. // TODO: Don't fetch the same URL twice. // This implementation doesn't do either: var crawlChild func(string, int) crawlChild = func(url string, depth int) { if depth <= 0 { return } body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) for _, u := range urls { go crawlChild(u, depth-1) } } crawlChild(url, depth) return }これで実行すると、1つしか出力されない。他の
goroutineを待たずして終わってしまうから。found: https://golang.org/ "The Go Programming Language"
chanelを使うように修正してみる。func Crawl(url string, depth int, fetcher Fetcher) { // TODO: Fetch URLs in parallel. // TODO: Don't fetch the same URL twice. // This implementation doesn't do either: // 追加 ch := make(chan string, depth*depth) var prev int var crawlChild func(string, int) crawlChild = func(url string, depth int) { if depth <= 0 { return } // 追加 ch <- url body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) for _, u := range urls { go crawlChild(u, depth-1) } } crawlChild(url, depth) // 追加 for { if len(ch) != prev { prev = len(ch) } else { break } time.Sleep(time.Millisecond) } return }
make()でサイズを指定しないと、deadlockとなるので指定してますが、適したサイズがわからないので、depth*depthとしてみた。
そして、return前に、本来の使い方ではないけど、chanel数がこれ以上増えないと分かるまで無限ループで待つようにする。この状態で実行すると、ようやっと最初と同じ結果が得られた。
あとは、URLを1度しか呼ばないようにするだけ。URLをkeyとした、map[string]boolで、URLを呼ぶ前にT/F判定させればよさそう。func Crawl(url string, depth int, fetcher Fetcher) { // TODO: Fetch URLs in parallel. // TODO: Don't fetch the same URL twice. // This implementation doesn't do either: // 追加 ch := make(chan string, depth*depth) var prev int // 追加 cache := make(map[string]bool) var mutex sync.Mutex var crawlChild func(string, int) crawlChild = func(url string, depth int) { if depth <= 0 { return } // 追加 if cache[url] == true { return } mutex.Lock() defer mutex.Unlock() cache[url] = true // 追加 ch <- url body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) for _, u := range urls { go crawlChild(u, depth-1) } } crawlChild(url, depth) // 追加 for { if len(ch) != prev { prev = len(ch) } else { break } time.Sleep(time.Millisecond) } return }実行すると、見事に1度しか呼ばれないようになった。
found: https://golang.org/ "The Go Programming Language" found: https://golang.org/pkg/ "Packages" found: https://golang.org/pkg/os/ "Package os" not found: https://golang.org/cmd/ found: https://golang.org/pkg/fmt/ "Package fmt" Program exited.全体のコードは、下記にあります。
a-tour-of-go/exercise-web-crawler.go at master · hirano00o/a-tour-of-go最後に
A Tour of Goは、中々に長い道のりですが、一通り基本は身につくので、やったほうが良いかと思います。
これが終わったら、実際になにか作るか、go Wiki(英語)、CodeReviewCommentsを見てくかですかね。CodeReviewCommentに関しては、和訳してくれた方がいるので、そちらを見ればいいのかなと思います。
参考
- 投稿日:2019-04-30T17:42:16+09:00
Dropbox SDK for Goを使ったファイルのアップロードやダウンロード
概要
Dropbox SDK for Goという非公式のパッケージを使ってドロップボックスにファイルのアップロードやダウンロードを行う方法を紹介します。
go version go1.12.3 darwin/amd64
パッケージのインストール
go get github.com/dropbox/dropbox-sdk-go-unofficial/dropboxインスタンスの生成
import "github.com/dropbox/dropbox-sdk-go-unofficial/dropbox" import "github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files" config := dropbox.Config{ Token: "DROPBOX_API_TOKEN", } client := files.New(config)ファイル一覧の取得
arg := files.NewListFolderArg("path") res, err := client.ListFolder(arg) if err != nil { panic(err) } for _, e := res.Entries { f, ok := e.(*files.FileMetadata) if ok { fmt.Println(f.Name) } }ファイルのダウンロード
arg := files.NewDownloadArg("filepath") _, err := client.Download(arg) if err != nil { panic(err) }ファイルのアップロード
新規ファイルのアップロード
arg := files.NewCommitInfo("filepath") // io.Reader型ならなんでも良い file, _ := os.Create("sample.txt") _, err := client.Upload(arg, file) if err := nil { panic(err) }既存ファイルへのアップロード(上書き)
arg := files.NewGetMetadataArg("filepath") res, err := client.GetMetadata(arg) if err != nil { panic(err) } var rev string m, ok := res.(*files.FileMetadata) if ok { rev = m.Rev } arg = files.NewCommitInfo("filepath") args.Mode = &files.WriteMode{Tagged: dropbox.Tagged{"update"}, Update: rev} file, _ := os.Create("sample.txt") _, err := client.Upload(arg, file) if err != nil { panic(err) }
- 投稿日:2019-04-30T08:01:29+09:00
Go言語テストメモ
◼️はじめに
Go 言語の学習をはじめた。
Go 言語でのテストの書き方をメモしておく。
◼️テスト実行コマンド
# カレントパッケージ内の全てのテストコードを実行 $ go test .上記のコマンドを実行すると
- カレントパッケージ内の全てのテストファイルをコンパイル
- テストコードからバイナリファイルを生成
- テストコード内の関数を実行
という、処理が流れる。
◼️テストファイル名
デフォルトだと、
*_test.goのファイル名を探す。◼️テスト設置場所
Go のテストファイルは、テスト対象のファイルと同じディレクトリに格納する。
例えば、abc パッケージ内に、hoge.go というファイルがあった場合、
test_hoge.go の格納場所は以下のようになる。abc L hoge.go L hoge_test.go◼️Go テスト関数
Go テスト関数のシグネチャは、以下の通り。
func Test<Name>(*testing.T)
<Name>には、任意の名前をつける事ができるが、大抵は、テスト対象の処理と関連性がある名前にする。◼️パッケージ構成
Go ファイルは、Package に所属する必要がある。
そのため、以下の構成にする。
testTutorial ├── main.go └── math ├── calc.go └── calc_test.go◼️テスト対象コードを準備
calc.go に平均値を求めるコードを記述する。
calc.gopackage math // 平均値を求める関数 func Average(s []int) int { total := 0 for _, i := range s { total += i } return int(total / len(s)) }上記は、スライスを引数として受け取り、平均値を計算するプログラム。
main.go は動作確認用に用意した。
main.gopackage main import ( "fmt" "github.com/hoge/testTutorial/math" ) func main() { s := []int{1, 2, 3, 4, 5} // Go だと、ローカル変数は、一文字にするのがよしとされているらしい(slice -> s) fmt.Println(math.Average(s)) }VS Code だと、Ctrl + F5 で、簡単にプログラムを実行できるので、おすすめ。
// 実行結果 3◼️テストコード
Package testing にテストの作法が乗っている。
calc.test.gopackage math import "testing" func TestAverage(t *testing.T) { v := Average([]int{1, 2, 3, 4, 5}) if v != 3 { t.Error("3が返却されるはず。", v) } }
t * testing.Tが、python で言う所の unittest だと思う。◼️ テストコードの実行
VS Code だと、テストファイル上に、「run test」 が表示される。
- Go というエクステンションを入れる必要があったと思う
run test を押下すると、テストが実行される。
Running tool: /usr/local/go/bin/go test -timeout 30s github.com/hogehoge/testTutorial/math -run ^(TestAverage)$ -v === RUN TestAverage --- PASS: TestAverage (0.00s) PASS ok github.com/hogehoge/testTutorial/math 0.047s Success: Tests passedなお、上記の表示結果は、-v オプションをつけているので、詳細な情報が表示されている。
コマンドライン上だと、以下のように実行する。
# テストコードのディレクトリに移動(testTutorial 上でも実行できる) $ pwd /Users/hoge/go/src/github.com/hogehoge/testTutorial/math # 全てのテストを実行する(... -> 全てのテストを実行) $ go test ./... ok github.com/hogehoge/testTutorial/math 0.049s $ go test ./... -v === RUN TestAverage --- PASS: TestAverage (0.00s) PASS ok github.com/hogehoge/testTutorial/math 0.034s◼️テストを失敗させる
テストを失敗させてみる。
calc_test.gopackage math import "testing" func TestAverage(t *testing.T) { v := Average([]int{10, 11, 12, 13, 14}) if v != 3 { t.Error("3が返却されるはず。", v) } }Running tool: /usr/local/go/bin/go test -timeout 30s github.com/hogehoge/testTutorial/math -run ^(TestAverage)$ -v === RUN TestAverage --- FAIL: TestAverage (0.00s) /Users/hoge/go/src/github.com/hogehoge/testTutorial/math/calc_test.go:8: 3が返却されるはず。 12 FAIL FAIL github.com/hogehoge/testTutorial/math 0.014s Error: Tests failed.◼️テストパッケージ
3rd-party のテストパッケージで、いつか使ってみたい。
◼️まとめ
Go では最初からテストの仕組みが組み込まれているから、ツールの統一ができる。
また、テストコード自体は書きやすいのではないかという印象を受けた。
- 投稿日:2019-04-30T00:19:18+09:00
Goで動的にioストリームの複製
備忘録的なアレ
io.ReadFullを使わないとゼロでパディングされて
動画などは正しく復号できなくなる
ライブ配信で使えそうなやつ
たまたまhttpのレスポンスを複製して別のhttpリクエストにコピーしてるだけで
基本どのioにも使える
別にchannelでパイプする必要もないけど、処理を分けたいからpackage main import ( "fmt" "io" "net/http" ) func writeStream(pipe chan *[]byte, writer map[*io.Writer]chan bool) { for buf := range pipe { for w, c := range writer { _, err := (*w).Write(*buf) if err != nil { close(c) delete(writer, w) } } } } func readStream(pipe chan *[]byte, writer map[*io.Writer]chan bool) { url := "" resp, err := http.Get(url) if err != nil { return } defer resp.Body.Close() size := 1024 for { if len(writer) == 0 { close(pipe) return } buf := make([]byte, size) _, err := io.ReadFull(resp.Body, buf) if err != nil { return } pipe <- &buf } } func main() { writer := make(map[*io.Writer]chan bool) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { var wb io.Writer = w writer[&wb] = make(chan bool) if len(writer) == 1 { pipe := make(chan *[]byte, 188*10000) go writeStream(pipe, writer) go readStream(pipe, writer) } <-writer[&wb] fmt.Println("done") }) http.ListenAndServe(":8000", nil) }


