Goならわかるシステムプログラミング:第3章(練習問題)

Q3.1 ファイルのコピー

package main

import (
    "io"
    "os"
)

func main() {
    file, err := os.Open("old.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    newFile, err := os.Create("new.txt")
    if err != nil {
        panic(err)
    }
    defer newFile.Close()
    io.Copy(newFile, file)
}

テスト

package main

import (
    "bytes"
    "io/ioutil"
    "os"
    "testing"
)

func exists(filename string)bool {
    _, err := os.Stat(filename)
    return err == nil
}

func TestMainSuccess(t *testing.T){
    if exists("new.txt") {
        os.Remove("new.txt")
    }

    main()
    oldFile, err := os.Open("old.txt")
    if err != nil {
        t.Fatalf("failed to open old file: %#v", err)
    }
    newFile, err := os.Open("new.txt")
    if err != nil {
        t.Fatalf("failed to open new file: %#v", err)
    }
    oldBuffer, err := ioutil.ReadAll(oldFile)
    if err != nil {
        t.Fatalf("failed to read content: %#v", err)
    }
    newBuffer, err := ioutil.ReadAll(newFile)
    if err != nil {
        t.Fatalf("failed to read content: %#v", err)
    }
    if bytes.Compare(oldBuffer, newBuffer) != 0 {
        t.Fatalf("each contents of files are different.")
    }

    os.Remove("new.txt")
}

テストはもうちょっとスッキリかける気がする。

Q3.2 テスト用の適切なサイズのファイルを生成

package main

import (
    "crypto/rand"
    "io"
    "os"
)

func main() {
    file, err := os.Create("samplefile")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    r := io.LimitedReader{R: rand.Reader, N: 1024}
    io.Copy(file, &r)
}

io.Copyを使わないように」と問題には書いてあったが、LimitReaderを使って読み出しているので問題ない?

テスト

package main

import (
    "os"
    "testing"
)

func TestMainSuccess(t *testing.T) {
    main()
    info, err := os.Stat("samplefile")
    if err != nil {
        t.Fatalf("could not open file.")
    }
    if info.Size() != 1024 {
        t.Fatal("size was not 1024 byte.")
    }
    defer os.Remove("samplefile")
}

ファイルサイズはos.Stat()で取れる。

Q3.4 zipファイルをウェブサーバーからダウンロード

package main

import (
    "io"
    "net/http"
    "os"
)

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8081", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/zip")
    w.Header().Set("Content-Disposition", "attachment; filename=ascii_sample.zip")
    file, err := os.Open("./chap3/test.zip")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    io.Copy(w, file)
}

Q3.5 CopyN

package main

import (
    "io"
    "os"
    "strings"
)

func main() {
    r := strings.NewReader("strings reader example")
    CopyN(os.Stdout, r, 5)
}

func CopyN(dst io.Writer, src io.Reader, n int64) (written int64, err error) {
    written, err = io.Copy(dst, io.LimitReader(src, n))
    if written == n {
        return n, nil
    }
    if written < n && err == nil {
        // src stopped early; must have been EOF.
        err = io.EOF
    }
    return
}

実装はこちらのコピー。 https://golang.org/src/io/io.go?s=11939:12009#L329

Q3.6 ストリーム総集編

package main

import (
    "io"
    "os"
    "strings"
)

var (
    computer = strings.NewReader("COMPUTER")
    system = strings.NewReader("SYSTEM")
    programming = strings.NewReader("PROGRAMMING")

)
func main() {
    var stream io.Reader

    charA := io.NewSectionReader(programming, 5, 1)
    charS := io.NewSectionReader(system, 0, 1)
    charC := io.NewSectionReader(computer, 0, 1)
    charI, pw := io.Pipe()
    w := io.MultiWriter(pw, pw)
    go func() {
        io.Copy(w, io.NewSectionReader(programming, 8, 1))
        pw.Close()
    }()
    stream = io.MultiReader(charA, charS, charC, charI)
    io.Copy(os.Stdout, stream)
}

同じwriterに2回書けるということに気づかず、時間を使ってしまった。 答えは下記のリンクにある(上記のものとは少し違う)

ASCII.jp:低レベルアクセスへの入り口(3):io.Reader後編 (2/2)|Goならわかるシステムプログラミング