diff --git a/kadai1/.gitkeep b/kadai2/.gitkeep similarity index 100% rename from kadai1/.gitkeep rename to kadai2/.gitkeep diff --git a/kadai2/en-ken/.gitignore b/kadai2/en-ken/.gitignore new file mode 100644 index 0000000..0a49fe5 --- /dev/null +++ b/kadai2/en-ken/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +out/ \ No newline at end of file diff --git a/kadai2/en-ken/README.md b/kadai2/en-ken/README.md new file mode 100644 index 0000000..6edda41 --- /dev/null +++ b/kadai2/en-ken/README.md @@ -0,0 +1,61 @@ +# 課題2 + +## io.Readerとio.Writerについて調べてみよう + +- 標準パッケージでどのように使われているか +- io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる + +### 標準パッケージで使われているところ + +- 基本的にI/Oが存在するところのI/Fには存在する。 + - stdin, stdout, stderr (os.Stdin, Stdout, Stderr) + - ファイル(os.File) + - メモリ(bytes.Buffer) + - TCP,UDP (net) + - POSTのbody (net.http) +- bufioではラップしてより使いやすくしている。 + +### 利点 + +- 理論的にはio.Reader/WriterのI/Fを実装しているどんなシステムにでも着替えられる。 + - 入力出力を抽象化できるので、呼び出し側は極論I/Oのシステムが何か意識しなくて良い。 + - 呼び出し側の実装が外界に依存しない。 + - DIPやりやすい。 + - 粗結合。移植観点からも有利。 + +## 1回目の宿題のテストを作ってみて下さい + +- [x] テストのしやすさを考えてリファクタリングしてみる +- [x] テストのカバレッジを取ってみる +- [x] テーブル駆動テストを行う +- [x] テストヘルパーを作ってみる + +## 使い方 + +```go +kadai2 -in [INPUT_EXT] -out [OUTPUT_EXT] SRC_DIR DST_DIR + +#Usage of kadai2: +# -in string +# input extension (jpg/png) (default "jpg") +# -out string +# output extension (jpg/png) (default "png") +``` + +## カバレッジ結果 + +```bash +> ./cov.sh +ok github.com/gopherdojo/dojo6/kadai2/en-ken/cli 0.001s coverage: 74.1% of statements +ok github.com/gopherdojo/dojo6/kadai2/en-ken/imgcnv 0.071s coverage: 85.0% of statements +ok github.com/gopherdojo/dojo6/kadai2/en-ken/kadai2 1.150s coverage: 0.0% of statements +``` + +## kadai1からの変更点 + +- mainをkadai2のディレクトリに変更 +- 冗長だった構造体のI/Fから関数のI/Fに変更 + - 関数のI/Fよき +- コマンド引数をシンプルに変更 +- 独自モックを実装 +- 独自実装していたファイル取得の再起処理を`filepath.Walk`に変更 diff --git a/kadai2/en-ken/cli/cli.go b/kadai2/en-ken/cli/cli.go new file mode 100644 index 0000000..f169fc9 --- /dev/null +++ b/kadai2/en-ken/cli/cli.go @@ -0,0 +1,69 @@ +package cli + +import ( + "flag" + "fmt" + "path/filepath" + "strings" + + "github.com/gopherdojo/dojo6/kadai2/en-ken/imgcnv" +) + +// CLI is for DI +type CLI struct { + AllFilePaths imgcnv.IAllFilePaths + NewImageFIle imgcnv.INewImageFile +} + +// Execute executes this app according to options +func (cli *CLI) Execute(args []string) error { + + var inputExt, outputExt string + flags := flag.NewFlagSet(args[0], flag.ExitOnError) + flags.StringVar(&inputExt, "in", "jpg", "input extension (jpg/png)") + flags.StringVar(&outputExt, "out", "png", "output extension (jpg/png)") + + // Parse command args + if err := flags.Parse(args[1:]); err != nil { + return err + } + + if len(flags.Args()) != 2 { + return fmt.Errorf("Either input directory or output directory not specified") + } + // Get input dir + inputDir, err := filepath.Abs(flags.Arg(0)) + if err != nil { + return err + } + // Get output dir + outputDir, err := filepath.Abs(flags.Arg(1)) + if err != nil { + return err + } + + // Get all file paths + paths, err := cli.AllFilePaths(inputDir, inputExt) + if err != nil { + return err + } + + // Convert and save + for _, path := range paths { + img, err := cli.NewImageFIle(path) + if err != nil { + return err + } + + // Copy the hierarchy of the input dir to that of the output dir. + outputPath := strings.Replace(img.AbsPath(), inputDir, outputDir, -1) + outputPath = strings.Replace(outputPath, inputExt, outputExt, 1) + + err = img.SaveAs(outputPath) + if err != nil { + return err + } + } + + return nil +} diff --git a/kadai2/en-ken/cli/cli_test.go b/kadai2/en-ken/cli/cli_test.go new file mode 100644 index 0000000..c55bdf2 --- /dev/null +++ b/kadai2/en-ken/cli/cli_test.go @@ -0,0 +1,102 @@ +package cli + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/gopherdojo/dojo6/kadai2/en-ken/imgcnv" +) + +type ImageFileMock struct { + t *testing.T + inputPath string + outputPath string +} + +func (mock *ImageFileMock) AbsPath() string { + return mock.inputPath +} + +func (mock *ImageFileMock) SaveAs(path string) error { + if path != mock.outputPath { + mock.t.Errorf("SaveAs was not called correctly:\nactual: %v\nexpected: %v", path, mock.outputPath) + } + return nil +} + +func TestExecuteSuccess(t *testing.T) { + tests := []struct { + argString string + inputDir string + inputExt string + outputDir string + outputExt string + }{ + { + argString: "./kadai1 ./testdata ./out", + inputDir: "./testdata", inputExt: "jpg", + outputDir: "./out", outputExt: "png", + }, + { + argString: "./kadai1 -in png ./testdata ./out", + inputDir: "./testdata", inputExt: "png", + outputDir: "./out", outputExt: "png", + }, + { + argString: "./kadai1 -out jpg ./testdata ./out", + inputDir: "./testdata", inputExt: "jpg", + outputDir: "./out", outputExt: "jpg", + }, + { + argString: "./kadai1 -in png -out jpg ./testdata ./out", + inputDir: "./testdata", inputExt: "png", + outputDir: "./out", outputExt: "jpg", + }, + } + + for _, test := range tests { + testName := "input: " + test.argString + t.Run(testName, func(t *testing.T) { + + inputAbsDir, _ := filepath.Abs(test.inputDir) + inputPath1 := inputAbsDir + "/test1" + test.inputExt + inputPath2 := inputAbsDir + "/test2" + test.inputExt + outputPath1, _ := filepath.Abs(test.outputDir + "/test1" + test.outputExt) + outputPath2, _ := filepath.Abs(test.outputDir + "/test2" + test.outputExt) + + allFilePathsMock := func(path string, ext string) ([]string, error) { + if path == inputAbsDir && ext == test.inputExt { + return []string{inputPath1, inputPath2}, nil + } + t.Errorf("AllFilePaths was not called correctly: %v %v", path, ext) + return nil, nil + } + + newImageFileMock := func(path string) (imgcnv.ImageFile, error) { + switch path { + case inputPath1: + return &ImageFileMock{ + t: t, + inputPath: path, + outputPath: outputPath1, + }, nil + case inputPath2: + return &ImageFileMock{ + t: t, + inputPath: path, + outputPath: outputPath2, + }, nil + } + return nil, nil + } + + cli := &CLI{ + AllFilePaths: allFilePathsMock, + NewImageFIle: newImageFileMock, + } + cli.Execute(strings.Split(test.argString, " ")) + + }) + } +} diff --git a/kadai2/en-ken/cov.sh b/kadai2/en-ken/cov.sh new file mode 100755 index 0000000..76ec5d5 --- /dev/null +++ b/kadai2/en-ken/cov.sh @@ -0,0 +1,2 @@ +#!/bin/bash +go test -cover ./... \ No newline at end of file diff --git a/kadai2/en-ken/go.mod b/kadai2/en-ken/go.mod new file mode 100644 index 0000000..7599e37 --- /dev/null +++ b/kadai2/en-ken/go.mod @@ -0,0 +1,3 @@ +module github.com/gopherdojo/dojo6/kadai2/en-ken + +go 1.12 diff --git a/kadai2/en-ken/go.sum b/kadai2/en-ken/go.sum new file mode 100644 index 0000000..c5455e1 --- /dev/null +++ b/kadai2/en-ken/go.sum @@ -0,0 +1,9 @@ +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/gopherdojo/dojo6 v0.0.0-20190710155631-1b40d3406c2f h1:UOg/ZpRwvokKADOjfCukjqQXYHb7HOdVSgZGABZc2BQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= diff --git a/kadai2/en-ken/imgcnv/dirpath.go b/kadai2/en-ken/imgcnv/dirpath.go new file mode 100644 index 0000000..468833b --- /dev/null +++ b/kadai2/en-ken/imgcnv/dirpath.go @@ -0,0 +1,40 @@ +package imgcnv + +import ( + "os" + "path/filepath" +) + +// IAllFilePaths is I/F of AllFilePaths +type IAllFilePaths func(path string, ext string) ([]string, error) + +const allocSize = 100 + +// AllFilePaths returns +// the paths of the files in the specified directory +// filtered by the specified extension. +func AllFilePaths(path string, ext string) ([]string, error) { + + result := make([]string, 0, allocSize) + err := filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + // Skip + return nil + } + + // Find by extension + isMatch, _ := filepath.Match("*."+ext, info.Name()) + if isMatch { + absPath, err := filepath.Abs(filePath) + result = append(result, absPath) + return err + } + return nil + }) + + return result, err +} diff --git a/kadai2/en-ken/imgcnv/dirpath_test.go b/kadai2/en-ken/imgcnv/dirpath_test.go new file mode 100644 index 0000000..bf7691b --- /dev/null +++ b/kadai2/en-ken/imgcnv/dirpath_test.go @@ -0,0 +1,50 @@ +package imgcnv + +import ( + "path/filepath" + "sort" + "testing" +) + +func TestAllFilePathsSuccess(t *testing.T) { + actual, _ := AllFilePaths("../testdata/", "jpg") + expected := []string{ + "../testdata/lenna_color.jpg", + "../testdata/lenna_gray.jpg", + "../testdata/layer1/girl_color.jpg", + "../testdata/layer1/girl_gray.jpg", + "../testdata/layer1/layer2/Mandrill.jpg", + } + for i := 0; i < len(expected); i++ { + expected[i], _ = filepath.Abs(expected[i]) + } + + if isEqualArray(actual, expected) == false { + t.Errorf("\nactual:%v\nexpected:%v", actual, expected) + } +} + +func TestAllFilePathsFailure(t *testing.T) { + _, err := AllFilePaths("../foo/", "jpg") + + if err == nil { + t.Errorf("directory does not exist") + } +} + +func compare(x string, y string) bool { + return x < y +} +func isEqualArray(array1 []string, array2 []string) bool { + sort.Slice(array1, func(i, j int) bool { return array1[i] > array1[j] }) + sort.Slice(array2, func(i, j int) bool { return array2[i] > array2[j] }) + if len(array1) != len(array2) { + return false + } + for i := 0; i < len(array1); i++ { + if array1[i] != array2[i] { + return false + } + } + return true +} diff --git a/kadai2/en-ken/imgcnv/imgfile.go b/kadai2/en-ken/imgcnv/imgfile.go new file mode 100644 index 0000000..07dacbb --- /dev/null +++ b/kadai2/en-ken/imgcnv/imgfile.go @@ -0,0 +1,85 @@ +package imgcnv + +import ( + "fmt" + "image" + "image/jpeg" + "image/png" + "os" + "path/filepath" +) + +// ImageFile expresses I/F to ImageFileStruct +type ImageFile interface { + AbsPath() string + SaveAs(path string) error +} + +// ImageFileStruct expresses the converting image +type ImageFileStruct struct { + image *image.Image + path string + ImageFile +} + +// INewImageFile is I/F to NewImageFile +type INewImageFile func(path string) (ImageFile, error) + +// NewImageFile is a constructor of ImageFile +func NewImageFile(path string) (ImageFile, error) { + absPath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + var image image.Image + switch filepath.Ext(path) { + case ".jpg", ".jpeg": + image, err = jpeg.Decode(file) + case ".png": + image, err = png.Decode(file) + default: + // try to decode as jpeg + image, err = jpeg.Decode(file) + } + if err != nil { + return nil, err + } + + return &ImageFileStruct{ + image: &image, + path: absPath, + }, nil +} + +// AbsPath returns the absolute path of the input file +func (img *ImageFileStruct) AbsPath() string { + return img.path +} + +// SaveAs oututs a file to the specified path after convering to the specified exteinsion. +func (img *ImageFileStruct) SaveAs(path string) error { + err := os.MkdirAll(filepath.Dir(path), 0777) + if err != nil { + return err + } + file, err := os.Create(path) + if err != nil { + return err + } + ext := filepath.Ext(path) + switch ext { + case ".jpg", ".jpeg": + return jpeg.Encode(file, *img.image, nil) + case ".png": + return png.Encode(file, *img.image) + default: + return fmt.Errorf("Unexpected extension") + } +} diff --git a/kadai2/en-ken/imgcnv/imgfile_test.go b/kadai2/en-ken/imgcnv/imgfile_test.go new file mode 100644 index 0000000..549d116 --- /dev/null +++ b/kadai2/en-ken/imgcnv/imgfile_test.go @@ -0,0 +1,108 @@ +package imgcnv + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" +) + +const inputDir = "../testdata/" +const outputDir = "../out/" + +func TestMain(m *testing.M) { + + os.RemoveAll(outputDir) + m.Run() + os.RemoveAll(outputDir) +} + +func TestConstructImageFileSuccess(t *testing.T) { + paths := []string{"layer1/layer2/Mandrill.jpg", "layer1/layer2/Mandrill.png"} + + for _, path := range paths { + inputPath := filepath.Join(inputDir, path) + _, err := NewImageFile(inputPath) + if err != nil { + t.Error(err) + } + } +} + +func TestConstructImageFileFailureWithInvalidPath(t *testing.T) { + inputPath := filepath.Join(inputDir, "layer1/layer2/layer3/foo.jpg") + _, err := NewImageFile(inputPath) + if err == nil { + t.Error(err) + } +} + +func TestConstructImageFileFailureWithOtherFormatFile(t *testing.T) { + inputPath := filepath.Join(inputDir, "lenna_color.gif") + _, err := NewImageFile(inputPath) + if err == nil { + t.Error(err) + } +} + +func TestAbsPathSuccess(t *testing.T) { + filePath := "layer1/layer2/Mandrill.jpg" + inputPath := filepath.Join(inputDir, filePath) + image, _ := NewImageFile(inputPath) + + absPath := image.AbsPath() + + if !strings.Contains(absPath, filePath) { + t.Error("Unexpected path", absPath) + } + if !exists(absPath) { + t.Error("Path does not exist", absPath) + } +} + +func TestSaveAsSuccess(t *testing.T) { + tests := []struct { + fileName string + ext string + expectedExt string + }{ + {fileName: "lenna_color", ext: ".png", expectedExt: ".jpg"}, + {fileName: "lenna_gray", ext: ".png", expectedExt: ".jpg"}, + {fileName: "lenna_color", ext: ".jpg", expectedExt: ".png"}, + {fileName: "lenna_gray", ext: ".jpg", expectedExt: ".png"}, + {fileName: "layer1/girl_color", ext: ".png", expectedExt: ".jpg"}, + {fileName: "layer1/girl_gray", ext: ".png", expectedExt: ".jpg"}, + {fileName: "layer1/girl_color", ext: ".jpg", expectedExt: ".png"}, + {fileName: "layer1/girl_gray", ext: ".jpg", expectedExt: ".png"}, + {fileName: "layer1/layer2/Mandrill", ext: ".png", expectedExt: ".jpg"}, + {fileName: "layer1/layer2/Mandrill", ext: ".jpg", expectedExt: ".png"}, + } + + for _, test := range tests { + testName := fmt.Sprintf("Test data: %v %v %v", test.fileName, test.ext, test.expectedExt) + t.Run(testName, func(t *testing.T) { + + inputPath := filepath.Join(inputDir, test.fileName+test.ext) + outputPath := filepath.Join(outputDir, test.fileName+test.expectedExt) + + imageFile, err := NewImageFile(inputPath) + if err != nil { + t.Error(err) + } + err = imageFile.SaveAs(outputPath) + if err != nil { + t.Error(err) + } + if !exists(outputPath) { + t.Error("No output files") + } + }) + } + +} + +func exists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} diff --git a/kadai2/en-ken/kadai2/main.go b/kadai2/en-ken/kadai2/main.go new file mode 100644 index 0000000..a6dd54b --- /dev/null +++ b/kadai2/en-ken/kadai2/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "os" + + "github.com/gopherdojo/dojo6/kadai2/en-ken/cli" + "github.com/gopherdojo/dojo6/kadai2/en-ken/imgcnv" +) + +func main() { + cli := &cli.CLI{ + AllFilePaths: imgcnv.IAllFilePaths(imgcnv.AllFilePaths), + NewImageFIle: imgcnv.INewImageFile(imgcnv.NewImageFile), + } + + err := cli.Execute(os.Args) + if err != nil { + fmt.Fprintf(os.Stderr, "ERR:%v", err) + os.Exit(-1) + } +} diff --git a/kadai2/en-ken/kadai2/main_test.go b/kadai2/en-ken/kadai2/main_test.go new file mode 100644 index 0000000..9fe95a9 --- /dev/null +++ b/kadai2/en-ken/kadai2/main_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "os/exec" + "strings" + "testing" +) + +func TestMain(m *testing.M) { + exec.Command("go", "get", "github.com/gopherdojo/dojo6/kadai2/en-ken/kadai2").Run() + m.Run() +} + +func TestMainSuccess(t *testing.T) { + + cmdString := "kadai2 ./testdata -input-ext .jpg -output-dir ./out -output-ext .png" + cmd := strings.Split(cmdString, " ") + err := exec.Command(cmd[0], cmd[1:]...).Run() + if err != nil { + t.Fatal(err) + } +} + +func TestMainFailure(t *testing.T) { + + cmdString := "kadai2" + cmd := strings.Split(cmdString, " ") + err := exec.Command(cmd[0], cmd[1:]...).Run() + if err == nil { + t.Fatal(err) + } +} diff --git a/kadai2/en-ken/testdata/layer1/girl_color.jpg b/kadai2/en-ken/testdata/layer1/girl_color.jpg new file mode 100644 index 0000000..d3482da Binary files /dev/null and b/kadai2/en-ken/testdata/layer1/girl_color.jpg differ diff --git a/kadai2/en-ken/testdata/layer1/girl_color.png b/kadai2/en-ken/testdata/layer1/girl_color.png new file mode 100644 index 0000000..18bc3fb Binary files /dev/null and b/kadai2/en-ken/testdata/layer1/girl_color.png differ diff --git a/kadai2/en-ken/testdata/layer1/girl_gray.jpg b/kadai2/en-ken/testdata/layer1/girl_gray.jpg new file mode 100644 index 0000000..24125a4 Binary files /dev/null and b/kadai2/en-ken/testdata/layer1/girl_gray.jpg differ diff --git a/kadai2/en-ken/testdata/layer1/girl_gray.png b/kadai2/en-ken/testdata/layer1/girl_gray.png new file mode 100644 index 0000000..85e8a4b Binary files /dev/null and b/kadai2/en-ken/testdata/layer1/girl_gray.png differ diff --git a/kadai2/en-ken/testdata/layer1/layer2/Mandrill.jpg b/kadai2/en-ken/testdata/layer1/layer2/Mandrill.jpg new file mode 100644 index 0000000..82b9981 Binary files /dev/null and b/kadai2/en-ken/testdata/layer1/layer2/Mandrill.jpg differ diff --git a/kadai2/en-ken/testdata/layer1/layer2/Mandrill.png b/kadai2/en-ken/testdata/layer1/layer2/Mandrill.png new file mode 100644 index 0000000..465eee3 Binary files /dev/null and b/kadai2/en-ken/testdata/layer1/layer2/Mandrill.png differ diff --git a/kadai2/en-ken/testdata/lena_color.gif b/kadai2/en-ken/testdata/lena_color.gif new file mode 100644 index 0000000..8103712 Binary files /dev/null and b/kadai2/en-ken/testdata/lena_color.gif differ diff --git a/kadai2/en-ken/testdata/lena_gray.gif b/kadai2/en-ken/testdata/lena_gray.gif new file mode 100644 index 0000000..045d327 Binary files /dev/null and b/kadai2/en-ken/testdata/lena_gray.gif differ diff --git a/kadai2/en-ken/testdata/lenna_color.jpg b/kadai2/en-ken/testdata/lenna_color.jpg new file mode 100644 index 0000000..2a4266d Binary files /dev/null and b/kadai2/en-ken/testdata/lenna_color.jpg differ diff --git a/kadai2/en-ken/testdata/lenna_color.png b/kadai2/en-ken/testdata/lenna_color.png new file mode 100644 index 0000000..4cea39a Binary files /dev/null and b/kadai2/en-ken/testdata/lenna_color.png differ diff --git a/kadai2/en-ken/testdata/lenna_gray.jpg b/kadai2/en-ken/testdata/lenna_gray.jpg new file mode 100644 index 0000000..6bd004a Binary files /dev/null and b/kadai2/en-ken/testdata/lenna_gray.jpg differ diff --git a/kadai2/en-ken/testdata/lenna_gray.png b/kadai2/en-ken/testdata/lenna_gray.png new file mode 100644 index 0000000..55efc7e Binary files /dev/null and b/kadai2/en-ken/testdata/lenna_gray.png differ