embed
を使えば簡単に解決する、という備忘録。
はじめに
フォントファイルなどのリソースファイルを雑に相対パスで指定したとき、go test
実行時にパスがズレておりno such file or directory
エラーになってしまう問題に遭遇しました。
embed
を使えば簡単に解決したので備忘録です。
リソースファイルの相対パスがテストでズレる問題: no such file or directory
問題
話をわかりやすくするため、問題を単純化/極端化して見ていきます。
. ├── main.go └── resource ├── font.go ├── font_test.go └── myfont.ttf
例えばこのようなディレクトリ構成において、font.go
にて下記のように雑に相対パスでttfフォントファイルを読み込むとします。
package resource import ( "log" "os" "golang.org/x/image/font" "golang.org/x/image/font/opentype" ) var ( FontFace font.Face ) func init() { ttf, err := os.ReadFile("resouce/myfont.ttf") // 相対パスでttfファイルを読み込む if err != nil { log.Fatalf("failed to read font file: %v", err) } tt, err := opentype.Parse(ttf) if err != nil { log.Fatalf("failed to parse font: %v", err) } const dpi = 72 FontFace, err = opentype.NewFace(tt, &opentype.FaceOptions{ Size: 50, DPI: dpi, Hinting: font.HintingFull, }) if err != nil { log.Fatalf("failed to create font face: %v", err) } }
これは、プロジェクトrootから実行すると問題なくttfフォントファイルが読み込まれます。
しかし、go test
によってテストコードを実行すると、この相対パスresouce/myfont.ttf
で指定したttfフォントファイルは読み込まれず、no such file or directory
エラーになります。
failed to read font file: open resouce/myfont.ttf: no such file or directory
原因
プロジェクトrootから実行するときと、go test
で実行されるときで、working directoryが異なることが原因です。
それぞれ、
- プロジェクトrootから実行するとき: プロジェクトroot
go test
で実行されるとき: テストファイルがあるディレクトリ
がworking directoryとなります。
※ これは下記のようにworking directoryを表示させれば簡単に確認できます。
func init() { cwd, err := os.Getwd() if err != nil { log.Fatalf("Error getting current working directory: %v", err) } log.Printf("Current working directory: %s", cwd) }
よってテストファイルがネストしたディレクトリ構造配下にある場合は、相対パスで指定するとテスト実行時にズレが生じる、という訳です。
解決策
embedを使うことで、この問題は簡単に解決します。
embedは、外部ファイルを実行バイナリに埋め込むことができる機能です。
package font import ( _ "embed" "log" "golang.org/x/image/font" "golang.org/x/image/font/opentype" ) var ( //go:embed myfont.ttf fontData []byte fontFace font.Face ) func init() { tt, err := opentype.Parse(fontData) if err != nil { log.Fatalf("failed to parse font: %v", err) } const dpi = 72 fontFace, err = opentype.NewFace(tt, &opentype.FaceOptions{ Size: 50, DPI: dpi, Hinting: font.HintingFull, }) if err != nil { log.Fatalf("failed to create font face: %v", err) } }
ここで、↓の部分でmyfont.ttf
ファイルがfontData
変数に埋め込まれるようになります。
//go:embed myfont.ttf fontData []byte
//go:embed
ディレクティブに与えるリソースファイルのパスは、そのコードのある場所からの相対パスになります。
これはコード実行時もテスト実行時も変わらないので、先述したような問題は発生しません。
おわりに
相対パスがテストでズレる問題を、embedで回避する方法をメモしました。
Working directoryをいじるとか、相対パスから絶対パスに変換しておくとかもありそうですが、embedを使うとシンプルに書けそうなので良いですね。
Go言語は初学者なので、まだまだ知らないことがたくさんありそうです。
[関連記事]
参考
embed package - embed - Go Packages
Go by Example: Embed Directive
ebiten/examples/resources/fonts/embed.go at main · hajimehoshi/ebiten · GitHub