Resolve "url-fetch"

This commit is contained in:
verytable 2020-02-13 19:54:24 +00:00
parent 8041717d54
commit df4a9d56c3
4 changed files with 244 additions and 0 deletions

View file

@ -8,3 +8,5 @@
score: 100 score: 100
- task: wordcount - task: wordcount
score: 100 score: 100
- task: urlfetch
score: 100

51
urlfetch/README.md Normal file
View file

@ -0,0 +1,51 @@
## urlfetch
В этой задаче нужно написать консольную утилиту,
которая принимает на вход произвольное количество http URL'ов и последоватльно скачивает их содержимое.
При обработке невалидного URL'а программа должна завершаться с ненулевым exit кодом.
### Примеры
Успешный запуск:
```
$ urlfetch https://golang.org https://go.dev
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<meta name="description" content="Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.">
...
```
Неуспешный запуск:
```
$ urlfetch golang.org
fetch: Get golang.org: unsupported protocol scheme ""
```
### Проверка решения
Для запуска тестов нужно выполнить следующую команду:
```
go test -v ./urlfetch/...
```
### Запуск программы
```
go run -v ./urlfetch/main.go
```
### Компиляция
```
go install ./urlfetch/...
```
После выполнения в `$GOPATH/bin` появится исполняемый файл с именем `urlfetch`.
### Walkthrough
1. Чтение аргументов командной строки: https://gobyexample.com/command-line-arguments
2. HTTP запрос: https://gobyexample.com/http-clients

7
urlfetch/main.go Normal file
View file

@ -0,0 +1,7 @@
// +build !solution
package main
func main() {
}

184
urlfetch/main_test.go Normal file
View file

@ -0,0 +1,184 @@
// +build !change
package main
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"strconv"
"sync"
"testing"
"github.com/stretchr/testify/require"
"gitlab.com/slon/shad-go/tools/testtool"
)
const urlfetchImportPath = "gitlab.com/slon/shad-go/urlfetch"
var binCache testtool.BinCache
func TestMain(m *testing.M) {
os.Exit(func() int {
var teardown testtool.CloseFunc
binCache, teardown = testtool.NewBinCache()
defer teardown()
return m.Run()
}())
}
func TestUrlFetch_valid(t *testing.T) {
binary, err := binCache.GetBinary(urlfetchImportPath)
require.Nil(t, err)
type endpoint string
type response string
for _, tc := range []struct {
name string
h http.HandlerFunc
queries []endpoint
expected []response
}{
{
name: "404",
h: func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "The requested URL was not found.", http.StatusNotFound)
},
queries: []endpoint{"/" + endpoint(testtool.RandomName())},
expected: []response{"The requested URL was not found."},
},
{
name: "200",
h: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("The requested URL was found.\n"))
},
queries: []endpoint{"/"},
expected: []response{"The requested URL was found.\n"},
},
{
name: "multiple-urls",
h: func(w http.ResponseWriter, r *http.Request) {
mux := http.NewServeMux()
mux.HandleFunc("/foo", func(w http.ResponseWriter, h *http.Request) {
_, _ = w.Write([]byte("foo"))
})
mux.HandleFunc("/bar", func(w http.ResponseWriter, h *http.Request) {
_, _ = w.Write([]byte("bar"))
})
mux.ServeHTTP(w, r)
},
queries: []endpoint{"/foo", "/bar"},
expected: []response{"foo", "bar"},
},
} {
t.Run(tc.name, func(t *testing.T) {
s := httptest.NewServer(tc.h)
defer s.Close()
urls := make([]string, len(tc.queries))
for i, q := range tc.queries {
urls[i] = s.URL + string(q)
}
cmd := exec.Command(binary, urls...)
cmd.Stdout = nil
cmd.Stderr = os.Stderr
data, err := cmd.Output()
require.Nil(t, err)
for _, r := range tc.expected {
require.True(t, bytes.Contains(data, []byte(r)),
fmt.Sprintf(`output="%s" does not contain expected response="%s"`, data, r))
}
})
}
}
func TestUrlFetch_malformed(t *testing.T) {
binary, err := binCache.GetBinary(urlfetchImportPath)
require.Nil(t, err)
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("success"))
}))
defer s.Close()
for _, tc := range []struct {
name string
urls []string
}{
{
name: "invalid-protocol-scheme",
urls: []string{"golang.org"},
},
{
name: "valid+invalid",
urls: []string{s.URL, "golang.org"},
},
} {
t.Run(tc.name, func(t *testing.T) {
cmd := exec.Command(binary, tc.urls...)
cmd.Stdout = nil
cmd.Stderr = os.Stderr
err = cmd.Run()
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
return
}
t.Fatalf("process ran with err=%v, want exit status != 0", err)
})
}
}
func TestUrlFetch_order(t *testing.T) {
binary, err := binCache.GetBinary(urlfetchImportPath)
require.Nil(t, err)
mux := http.NewServeMux()
var expectedCallOrder []int
var mu sync.Mutex
var callOrder []int
n := 1000
for i := 0; i < n; i++ {
i := i
expectedCallOrder = append(expectedCallOrder, i)
s := strconv.Itoa(i)
mux.HandleFunc("/"+s, func(w http.ResponseWriter, h *http.Request) {
mu.Lock()
callOrder = append(callOrder, i)
mu.Unlock()
_, _ = w.Write([]byte(s))
})
}
s := httptest.NewServer(mux)
defer s.Close()
urls := make([]string, n)
for i := 0; i < n; i++ {
urls[i] = s.URL + "/" + strconv.Itoa(i)
}
cmd := exec.Command(binary, urls...)
cmd.Stdout = nil
cmd.Stderr = os.Stderr
require.Nil(t, cmd.Run())
mu.Lock()
defer mu.Unlock()
require.Equal(t, expectedCallOrder, callOrder)
}