<commit_before>package main

import (
	"encoding/json"
	"flag"
	"io/ioutil"
	"net/http"
	"os"
	"testing"

	_ "github.com/lib/pq"
)

var (
	integration = flag.Bool("integration", false, "run integration tests")
)

// testHost is host url for test
const testHost = "http://localhost:1104"

// testEndpoint is endpoint for API
const testEndpoint = testHost + "/v1"

// testMime is request content-type
const testMime = "application/json"

func TestMain(m *testing.M) {
	flag.Parse()
	result := m.Run()
	os.Exit(result)
}

func TestGetUserList(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/users")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetUserList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var users interface{}
	err = json.Unmarshal(body, &users)
	if err != nil {
		t.Fatal(err)
	}

	expectedCount := 2
	gotCount := len(users.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetUsers API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}

	expectedHighestID := "10002"
	gotHighestID := res.Header.Get("User-Highest-Id")
	if gotHighestID != expectedHighestID {
		t.Errorf("GetUsers API returned wrong highest id: got %v expected %v",
			gotHighestID, expectedHighestID)
	}

	expectedLowestID := "10001"
	gotLowestID := res.Header.Get("User-Lowest-Id")
	if gotLowestID != expectedLowestID {
		t.Errorf("GetUsers API returned wrong lowest id: got %v expected %v",
			gotLowestID, expectedLowestID)
	}
}

func TestGetUserByName(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/users?name=foo")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetUserList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var users interface{}
	err = json.Unmarshal(body, &users)
	if err != nil {
		t.Fatal(err)
	}

	fst := users.([]interface{})[0].(map[string]interface{})

	expectedName := "foo"
	gotName, ok := fst["name"].(string)
	if ok && gotName != expectedName {
		t.Errorf("GetUsers API returned wrong user: got %s expected %s",
			gotName, expectedName)
	}

	expectedCount := 1
	gotCount := len(users.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetUsers API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}
}

func TestGetUserByID(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/users?id=10001")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetUserList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var users interface{}
	err = json.Unmarshal(body, &users)
	if err != nil {
		t.Fatal(err)
	}

	fst := users.([]interface{})[0].(map[string]interface{})

	expectedName := "test"
	gotName, ok := fst["name"].(string)
	if ok && gotName != expectedName {
		t.Errorf("GetUsers  API returned wrong user: got %s expected %s",
			gotName, expectedName)
	}

	expectedCount := 1
	gotCount := len(users.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetUsers API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}
}

func TestGetGroupList(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/groups")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetGroupList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var groups interface{}
	err = json.Unmarshal(body, &groups)
	if err != nil {
		t.Fatal(err)
	}

	expectedCount := 2
	gotCount := len(groups.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetGroups API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}

	expectedHighestID := "10002"
	gotHighestID := res.Header.Get("Group-Highest-Id")
	if gotHighestID != expectedHighestID {
		t.Errorf("GetGroups API returned wrong highest id: got %v expected %v",
			gotHighestID, expectedHighestID)
	}

	expectedLowestID := "10001"
	gotLowestID := res.Header.Get("Group-Lowest-Id")
	if gotLowestID != expectedLowestID {
		t.Errorf("GetGroups API returned wrong lowest id: got %v expected %v",
			gotLowestID, expectedLowestID)
	}
}

func TestGetGroupByName(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/groups?name=bar")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetGroupList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var groups interface{}
	err = json.Unmarshal(body, &groups)
	if err != nil {
		t.Fatal(err)
	}

	fst := groups.([]interface{})[0].(map[string]interface{})

	expectedName := "bar"
	gotName, ok := fst["name"].(string)
	if ok && gotName != expectedName {
		t.Errorf("GetGroups API returned wrong group: got %s expected %s",
			gotName, expectedName)
	}

	expectedCount := 1
	gotCount := len(groups.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetGroups API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}
}

func TestGetGroupByID(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/groups?id=10001")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetGroupList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var groups interface{}
	err = json.Unmarshal(body, &groups)
	if err != nil {
		t.Fatal(err)
	}

	fst := groups.([]interface{})[0].(map[string]interface{})

	expectedName := "test"
	gotName, ok := fst["name"].(string)
	if ok && gotName != expectedName {
		t.Errorf("GetGroups  API returned wrong group: got %s expected %s",
			gotName, expectedName)
	}

	expectedCount := 1
	gotCount := len(groups.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetGroups API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}
}

func TestRoot(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testHost + "/")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("STNS Status API returned wrong status code: got %v want %v", res.StatusCode, http.StatusOK)
	}

	expected := "Hello! STNS!!1"
	got := string(body)
	if got != expected {
		t.Errorf("STNS Status API returned wrong body: got %v expected %v", got, expected)
	}
}
<commit_msg>fix integration test<commit_after>package main

import (
	"encoding/json"
	"flag"
	"io/ioutil"
	"net/http"
	"os"
	"testing"

	_ "github.com/lib/pq"
)

var (
	integration = flag.Bool("integration", false, "run integration tests")
)

// testHost is host url for test
const testHost = "http://localhost:1104"

// testEndpoint is endpoint for API
const testEndpoint = testHost + "/v1"

// testMime is request content-type
const testMime = "application/json"

func TestMain(m *testing.M) {
	flag.Parse()
	result := m.Run()
	os.Exit(result)
}

func TestGetUserList(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/users")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetUserList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var users interface{}
	err = json.Unmarshal(body, &users)
	if err != nil {
		t.Fatal(err)
	}

	expectedCount := 3
	gotCount := len(users.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetUsers API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}

	expectedHighestID := "10002"
	gotHighestID := res.Header.Get("User-Highest-Id")
	if gotHighestID != expectedHighestID {
		t.Errorf("GetUsers API returned wrong highest id: got %v expected %v",
			gotHighestID, expectedHighestID)
	}

	expectedLowestID := "99"
	gotLowestID := res.Header.Get("User-Lowest-Id")
	if gotLowestID != expectedLowestID {
		t.Errorf("GetUsers API returned wrong lowest id: got %v expected %v",
			gotLowestID, expectedLowestID)
	}
}

func TestGetUserByName(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/users?name=foo")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetUserList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var users interface{}
	err = json.Unmarshal(body, &users)
	if err != nil {
		t.Fatal(err)
	}

	fst := users.([]interface{})[0].(map[string]interface{})

	expectedName := "foo"
	gotName, ok := fst["name"].(string)
	if ok && gotName != expectedName {
		t.Errorf("GetUsers API returned wrong user: got %s expected %s",
			gotName, expectedName)
	}

	expectedCount := 1
	gotCount := len(users.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetUsers API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}
}

func TestGetUserByID(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/users?id=10001")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetUserList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var users interface{}
	err = json.Unmarshal(body, &users)
	if err != nil {
		t.Fatal(err)
	}

	fst := users.([]interface{})[0].(map[string]interface{})

	expectedName := "test"
	gotName, ok := fst["name"].(string)
	if ok && gotName != expectedName {
		t.Errorf("GetUsers  API returned wrong user: got %s expected %s",
			gotName, expectedName)
	}

	expectedCount := 1
	gotCount := len(users.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetUsers API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}
}

func TestGetGroupList(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/groups")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetGroupList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var groups interface{}
	err = json.Unmarshal(body, &groups)
	if err != nil {
		t.Fatal(err)
	}

	expectedCount := 2
	gotCount := len(groups.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetGroups API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}

	expectedHighestID := "10002"
	gotHighestID := res.Header.Get("Group-Highest-Id")
	if gotHighestID != expectedHighestID {
		t.Errorf("GetGroups API returned wrong highest id: got %v expected %v",
			gotHighestID, expectedHighestID)
	}

	expectedLowestID := "10001"
	gotLowestID := res.Header.Get("Group-Lowest-Id")
	if gotLowestID != expectedLowestID {
		t.Errorf("GetGroups API returned wrong lowest id: got %v expected %v",
			gotLowestID, expectedLowestID)
	}
}

func TestGetGroupByName(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/groups?name=bar")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetGroupList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var groups interface{}
	err = json.Unmarshal(body, &groups)
	if err != nil {
		t.Fatal(err)
	}

	fst := groups.([]interface{})[0].(map[string]interface{})

	expectedName := "bar"
	gotName, ok := fst["name"].(string)
	if ok && gotName != expectedName {
		t.Errorf("GetGroups API returned wrong group: got %s expected %s",
			gotName, expectedName)
	}

	expectedCount := 1
	gotCount := len(groups.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetGroups API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}
}

func TestGetGroupByID(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testEndpoint + "/groups?id=10001")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("GetGroupList API returned wrong status code: got %v want %v",
			res.StatusCode, http.StatusOK)
	}

	var groups interface{}
	err = json.Unmarshal(body, &groups)
	if err != nil {
		t.Fatal(err)
	}

	fst := groups.([]interface{})[0].(map[string]interface{})

	expectedName := "test"
	gotName, ok := fst["name"].(string)
	if ok && gotName != expectedName {
		t.Errorf("GetGroups  API returned wrong group: got %s expected %s",
			gotName, expectedName)
	}

	expectedCount := 1
	gotCount := len(groups.([]interface{}))
	if gotCount != expectedCount {
		t.Errorf("GetGroups API returned wrong count: got %v expected %v",
			gotCount, expectedCount)
	}
}

func TestRoot(t *testing.T) {
	if !*integration {
		t.Skip()
	}

	res, err := http.Get(testHost + "/")
	if err != nil {
		t.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		t.Errorf("STNS Status API returned wrong status code: got %v want %v", res.StatusCode, http.StatusOK)
	}

	expected := "Hello! STNS!!1"
	got := string(body)
	if got != expected {
		t.Errorf("STNS Status API returned wrong body: got %v expected %v", got, expected)
	}
}
<|endoftext|>


<commit_before>// +build integration

package main

import (
	"os"
	"os/exec"
	"testing"
)

var artifacts = []struct {
	in  string
	out string
}{
	{"fixtures/virtualbox-ovf.json", "packer_virtualbox-ovf_virtualbox.vhd"},
	{"fixtures/virtualbox-ova.json", "packer_virtualbox-ova_virtualbox.vhd"},
}

func TestIntegration(t *testing.T) {
	if err := os.Chdir("test"); err != nil {
		t.Error(err)
	}
	for _, tt := range artifacts {
		cmd := exec.Command("packer", "build", "--force", tt.in)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err := cmd.Run()
		if err != nil {
			t.Error(err)
		}
		if _, err := os.Stat(tt.out); os.IsNotExist(err) {
			t.Error(err)
		}
	}
}
<commit_msg>Set up VirtualBox integration test with OVA/OVF artifacts<commit_after>// +build integration

package main

import (
	"log"
	"os"
	"os/exec"
	"testing"
)

var artifacts = []struct {
	in  string
	out string
}{
	{"fixtures/virtualbox-ovf.json", "packer_virtualbox-ovf_virtualbox.vhd"},
	{"fixtures/virtualbox-ova.json", "packer_virtualbox-ova_virtualbox.vhd"},
}

// Build the source artifacts.
func init() {
	cmd := exec.Command("packer", "build", "--force", "fixtures/virtualbox-iso.json")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
}

func TestIntegration(t *testing.T) {
	if err := os.Chdir("test"); err != nil {
		t.Error(err)
	}
	for _, tt := range artifacts {
		cmd := exec.Command("packer", "build", "--force", tt.in)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err := cmd.Run()
		if err != nil {
			t.Error(err)
		}
		if _, err := os.Stat(tt.out); os.IsNotExist(err) {
			t.Error(err)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

func run(t *testing.T, cmd string) *exec.Cmd {
	return runInDir(t, cmd, "")
}

func runInTestApp(t *testing.T, cmd string) *exec.Cmd {
	return runInDir(t, cmd, "testapp")
}

func runInDir(t *testing.T, cmdString, dir string) *exec.Cmd {
	words := strings.Split(cmdString, " ")
	cmd := exec.Command(words[0], words[1:]...)
	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	fmt.Printf("[%s]\n", cmdString)
	err := cmd.Run()
	if err != nil {
		t.Fatalf("Failed to run '%s': %s\n", cmd, err)
	}
	return cmd
}

func startInDir(t *testing.T, cmdString, dir string) *exec.Cmd {
	words := strings.Split(cmdString, " ")
	cmd := exec.Command(words[0], words[1:]...)
	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	fmt.Printf("[%s]\n", cmdString)
	err := cmd.Start()
	if err != nil {
		t.Fatalf("Failed to start '%s': %s\n", cmd, err)
	}
	return cmd
}

func TestEverything(t *testing.T) {
	cwd, err := filepath.Abs(".")
	if err != nil {
		t.Fatalf("%s", err)
	}
	deployDir, err := ioutil.TempDir("/tmp", "camustest-")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %s\n", err)
	}
	run(t, "go build")
	server := startInDir(t, cwd+"/camus -server", deployDir)
	defer server.Process.Kill()
	time.Sleep(1 * time.Second)

	os.Chdir("testapp")

	client, err := NewClientImpl(".")
	if err != nil {
		t.Fatalf("%s", err)
	}

	_, err = client.Build()
	if err != nil {
		t.Fatalf("client build: %s", err)
	}
	oldDeploys, err := client.ListDeploys()
	if err != nil {
		t.Fatalf("client list deploys: %s", err)
	}
	deployId, err := client.Push("prod")
	if err != nil {
		t.Fatalf("client push: %s", err)
	}
	deploys, err := client.ListDeploys()
	if err != nil {
		t.Fatalf("client list deploys: %s", err)
	}
	if findDeployById(deployId, oldDeploys) {
		t.Fatal("newly minted deploy shouldn't have been in the old deploy list")
	}
	if !findDeployById(deployId, deploys) {
		t.Fatal("newly minted deploy should be in the new deploy list")
	}
	if len(deploys) != len(oldDeploys)+1 {
		t.Fatalf("expected %d deploys, but got %d", len(oldDeploys)+1, len(deploys))
	}
}

func findDeployById(deployId string, deploys []*Deploy) bool {
	for _, deploy := range deploys {
		if deploy.Id == deployId {
			return true
		}
	}
	return false
}
<commit_msg>make integration test run<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

func run(t *testing.T, cmd string) *exec.Cmd {
	return runInDir(t, cmd, "")
}

func runInTestApp(t *testing.T, cmd string) *exec.Cmd {
	return runInDir(t, cmd, "testapp")
}

func runInDir(t *testing.T, cmdString, dir string) *exec.Cmd {
	words := strings.Split(cmdString, " ")
	cmd := exec.Command(words[0], words[1:]...)
	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	fmt.Printf("[%s]\n", cmdString)
	err := cmd.Run()
	if err != nil {
		t.Fatalf("Failed to run '%s': %s\n", cmd, err)
	}
	return cmd
}

func startInDir(t *testing.T, cmdString, dir string) *exec.Cmd {
	words := strings.Split(cmdString, " ")
	cmd := exec.Command(words[0], words[1:]...)
	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	fmt.Printf("[%s]\n", cmdString)
	err := cmd.Start()
	if err != nil {
		t.Fatalf("Failed to start '%s': %s\n", cmd, err)
	}
	return cmd
}

func TestEverything(t *testing.T) {
	cwd, err := filepath.Abs(".")
	if err != nil {
		t.Fatalf("%s", err)
	}
	deployDir, err := ioutil.TempDir("/tmp", "camustest-")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %s\n", err)
	}
	run(t, "go build")
	server := startInDir(t, cwd+"/camus -server", deployDir)
	defer server.Process.Kill()
	time.Sleep(1 * time.Second)

	os.Chdir("testapp")

	client, err := NewClientImpl()
	if err != nil {
		t.Fatalf("%s", err)
	}

	_, err = client.Build()
	if err != nil {
		t.Fatalf("client build: %s", err)
	}
	oldDeploys, err := client.ListDeploys()
	if err != nil {
		t.Fatalf("client list deploys: %s", err)
	}
	deployId, err := client.Push("prod")
	if err != nil {
		t.Fatalf("client push: %s", err)
	}
	deploys, err := client.ListDeploys()
	if err != nil {
		t.Fatalf("client list deploys: %s", err)
	}
	if findDeployById(deployId, oldDeploys) {
		t.Fatal("newly minted deploy shouldn't have been in the old deploy list")
	}
	if !findDeployById(deployId, deploys) {
		t.Fatal("newly minted deploy should be in the new deploy list")
	}
	if len(deploys) != len(oldDeploys)+1 {
		t.Fatalf("expected %d deploys, but got %d", len(oldDeploys)+1, len(deploys))
	}
}

func findDeployById(deployId string, deploys []*Deploy) bool {
	for _, deploy := range deploys {
		if deploy.Id == deployId {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package cache

import (
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"io/ioutil"

	"github.com/mholt/caddy/caddyhttp/httpserver"
	"github.com/stretchr/testify/require"
)

func doRequest(t *testing.T, h httpserver.Handler) (*http.Response, error) {
	w := httptest.NewRecorder()
	r, urlErr := http.NewRequest("GET", "/", nil)
	require.NoError(t, urlErr)

	_, err := h.ServeHTTP(w, r)
	return w.Result(), err
}

func requireStatus(t *testing.T, r *http.Response, status string) {
	require.Equal(t, status, r.Header.Get("X-cache-status"))
}

func requireCode(t *testing.T, r *http.Response, expectedCode int) {
	require.Equal(t, expectedCode, r.StatusCode)
}

func requireBody(t *testing.T, r *http.Response, expectedBody []byte) {
	actualBody, err := ioutil.ReadAll(r.Body)
	require.NoError(t, err)
	require.Equal(t, expectedBody, actualBody)
}

func requestAndAssert(t *testing.T, h httpserver.Handler, expectedCode int, expectedStatus string, expectedBody []byte) {
	response, _ := doRequest(t, h)
	requireCode(t, response, expectedCode)
	requireStatus(t, response, expectedStatus)
	requireBody(t, response, expectedBody)
}

func TestPublicResponseWithBody(t *testing.T) {
	content := []byte("abc")
	hits := 0
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		hits++
		w.Header().Add("Cache-control", "max-age=1")
		w.Write(content)
		return 200, nil
	}), emptyConfig())

	requestAndAssert(t, h, 200, cacheMiss, content)
	requestAndAssert(t, h, 200, cacheHit, content)
	require.Equal(t, 1, hits)
}

func TestPublicResponseWithoutBody(t *testing.T) {
	hits := 0
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		hits++
		w.Header().Add("Cache-control", "max-age=1")
		return 200, nil
	}), emptyConfig())

	requestAndAssert(t, h, 200, cacheMiss, []byte{})
	requestAndAssert(t, h, 200, cacheHit, []byte{})
	require.Equal(t, 1, hits)
}

func TestPrivateResponseWithBody(t *testing.T) {
	content := []byte("abc")
	hits := 0
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		hits++
		w.Header().Add("Cache-control", "private")
		w.Write(content)
		return 200, nil
	}), emptyConfig())

	requestAndAssert(t, h, 200, cacheMiss, content)
	requestAndAssert(t, h, 200, cacheSkip, content)
	require.Equal(t, 2, hits)
}

func TestPrivateResponseWithoutBody(t *testing.T) {
	hits := 0
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		hits++
		w.Header().Add("Cache-control", "private")
		return 200, nil
	}), emptyConfig())

	requestAndAssert(t, h, 200, cacheMiss, []byte{})
	requestAndAssert(t, h, 200, cacheSkip, []byte{})
	require.Equal(t, 2, hits)
}

func TestHandlerError(t *testing.T) {
	badGateway := errors.New("Bad gateway")
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		return 503, badGateway
	}), emptyConfig())

	_, err1 := doRequest(t, h)
	require.Equal(t, badGateway, err1)

	_, err2 := doRequest(t, h)
	require.Equal(t, badGateway, err2)
}
<commit_msg>Add more integration tests<commit_after>package cache

import (
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"io/ioutil"

	"github.com/mholt/caddy/caddyhttp/httpserver"
	"github.com/stretchr/testify/require"
)

func doRequestWithHeaders(t *testing.T, h httpserver.Handler, headers http.Header) (*http.Response, error) {
	w := httptest.NewRecorder()
	r, urlErr := http.NewRequest("GET", "/", nil)
	require.NoError(t, urlErr)

	r.Header = headers

	_, err := h.ServeHTTP(w, r)
	return w.Result(), err
}

func doRequestTo(t *testing.T, to string, h httpserver.Handler) (*http.Response, error) {
	w := httptest.NewRecorder()
	r, urlErr := http.NewRequest("GET", to, nil)
	require.NoError(t, urlErr)

	_, err := h.ServeHTTP(w, r)
	return w.Result(), err
}

func doRequest(t *testing.T, h httpserver.Handler) (*http.Response, error) {
	return doRequestWithHeaders(t, h, http.Header{})
}

func requireStatus(t *testing.T, r *http.Response, status string) {
	require.Equal(t, status, r.Header.Get(defaultStatusHeader))
}

func requireCode(t *testing.T, r *http.Response, expectedCode int) {
	require.Equal(t, expectedCode, r.StatusCode)
}

func requireBody(t *testing.T, r *http.Response, expectedBody []byte) {
	actualBody, err := ioutil.ReadAll(r.Body)
	require.NoError(t, err)
	require.Equal(t, expectedBody, actualBody)
}

func requestAndAssert(t *testing.T, h httpserver.Handler, headers http.Header, expectedCode int, expectedStatus string, expectedBody []byte) {
	response, _ := doRequestWithHeaders(t, h, headers)
	requireCode(t, response, expectedCode)
	requireStatus(t, response, expectedStatus)
	requireBody(t, response, expectedBody)
}

func TestPublicResponseWithBody(t *testing.T) {
	content := []byte("abc")
	hits := 0
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		hits++
		w.Header().Add("Cache-control", "max-age=1")
		w.Write(content)
		return 200, nil
	}), emptyConfig())

	requestAndAssert(t, h, http.Header{}, 200, cacheMiss, content)
	requestAndAssert(t, h, http.Header{}, 200, cacheHit, content)
	require.Equal(t, 1, hits)
}

func TestPublicResponseWithoutBody(t *testing.T) {
	hits := 0
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		hits++
		w.Header().Add("Cache-control", "max-age=1")
		return 200, nil
	}), emptyConfig())

	requestAndAssert(t, h, http.Header{}, 200, cacheMiss, []byte{})
	requestAndAssert(t, h, http.Header{}, 200, cacheHit, []byte{})
	require.Equal(t, 1, hits)
}

func TestPrivateResponseWithBody(t *testing.T) {
	content := []byte("abc")
	hits := 0
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		hits++
		w.Header().Add("Cache-control", "private")
		w.Write(content)
		return 200, nil
	}), emptyConfig())

	requestAndAssert(t, h, http.Header{}, 200, cacheMiss, content)
	requestAndAssert(t, h, http.Header{}, 200, cacheSkip, content)
	require.Equal(t, 2, hits)
}

func TestPrivateResponseWithoutBody(t *testing.T) {
	hits := 0
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		hits++
		w.Header().Add("Cache-control", "private")
		return 200, nil
	}), emptyConfig())

	requestAndAssert(t, h, http.Header{}, 200, cacheMiss, []byte{})
	requestAndAssert(t, h, http.Header{}, 200, cacheSkip, []byte{})
	require.Equal(t, 2, hits)
}

func TestHandlerError(t *testing.T) {
	badGateway := errors.New("Bad gateway")
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		return 503, badGateway
	}), emptyConfig())

	_, err1 := doRequest(t, h)
	require.Equal(t, badGateway, err1)

	_, err2 := doRequest(t, h)
	require.Equal(t, badGateway, err2)
}

func TestPublicResponseWithVaryHeader(t *testing.T) {
	content := []byte("abc")
	hits := 0
	h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
		hits++
		w.Header().Add("Cache-control", "max-age=10")
		w.Header().Add("Vary", "Accept-Encoding")
		w.Write(content)
		return 200, nil
	}), emptyConfig())

	gzip := http.Header{"Accept-Encoding": []string{"gzip"}}
	requestAndAssert(t, h, gzip, 200, cacheMiss, content)
	requestAndAssert(t, h, gzip, 200, cacheHit, content)
	requestAndAssert(t, h, gzip, 200, cacheHit, content)
	require.Equal(t, 1, hits)

	deflate := http.Header{"Accept-Encoding": []string{"deflate"}}
	requestAndAssert(t, h, deflate, 200, cacheMiss, content)
	requestAndAssert(t, h, deflate, 200, cacheHit, content)
	requestAndAssert(t, h, deflate, 200, cacheHit, content)
	require.Equal(t, 2, hits)

	both := http.Header{"Accept-Encoding": []string{"gzip,defalte"}}
	requestAndAssert(t, h, both, 200, cacheMiss, content)
	requestAndAssert(t, h, both, 200, cacheHit, content)
	requestAndAssert(t, h, both, 200, cacheHit, content)
	require.Equal(t, 3, hits)
}

func TestConfigRules(t *testing.T) {
	content := []byte("abc")
	config := emptyConfig()
	config.CacheRules = []CacheRule{
		&PathCacheRule{Path: "/assets"},
		&HeaderCacheRule{Header: "X-Custom-header", Value: []string{"a", "b"}},
	}

	t.Run("path", func(t *testing.T) {
		hits := 0

		h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
			hits++
			w.Write(content)
			return 200, nil
		}), config)

		doRequestTo(t, "/private", h)
		doRequestTo(t, "/private", h)
		require.Equal(t, 2, hits)

		doRequestTo(t, "/assets", h)
		doRequestTo(t, "/assets", h)
		require.Equal(t, 3, hits)
	})

	t.Run("header matched", func(t *testing.T) {
		hits := 0

		h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
			hits++
			w.Header().Add("X-Custom-header", "a")
			w.Write(content)
			return 200, nil
		}), config)

		doRequest(t, h)
		doRequest(t, h)
		require.Equal(t, 1, hits)
	})

	t.Run("header not matched", func(t *testing.T) {
		hits := 0

		h := NewHandler(httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
			hits++
			w.Header().Add("X-Custom-header", "c")
			w.Write(content)
			return 200, nil
		}), config)

		doRequest(t, h)
		doRequest(t, h)
		require.Equal(t, 2, hits)
	})
}
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package interfaces

import (
	"fmt"
	"strings"
	"time"

	"github.com/sschepens/gohistogram"
	"github.com/sschepens/pb"
	"github.com/mercadolibre/pla/boomer"
)

const (
	barChar = "∎"
)

// BasicInterface is Pla's default text-based terminal interface.
type BasicInterface struct {
	avgTotal float64
	fastest  float64
	slowest  float64
	average  float64
	rps      float64

	start time.Time
	total time.Duration

	errorDist      map[string]int
	statusCodeDist map[int]int
	sizeTotal      int64

	boom  *boomer.Boomer
	histo *gohistogram.NumericHistogram
	bar   *pb.ProgressBar
	pct   int
}

// NewBasicInterface instantiates a new BasicInterface.
func NewBasicInterface() *BasicInterface {
	return &BasicInterface{
		start:          time.Now(),
		statusCodeDist: make(map[int]int),
		errorDist:      make(map[string]int),
		histo:          gohistogram.NewHistogram(10),
	}
}

// Start initializes interface
func (b *BasicInterface) Start(boom *boomer.Boomer) {
	b.boom = boom
	b.initProgressBar()
}

// ProcessResult increments ProgressBar and keeps track of statistics.
func (b *BasicInterface) ProcessResult(res boomer.Result) {
	if res.Err != nil {
		b.errorDist[res.Err.Error()]++
	} else {
		sec := res.Duration.Seconds()
		if b.slowest == 0 || sec > b.slowest {
			b.slowest = sec
		}
		if b.fastest == 0 || b.fastest > sec {
			b.fastest = sec
		}
		b.histo.Add(res.Duration.Seconds())
		b.avgTotal += res.Duration.Seconds()
		b.statusCodeDist[res.StatusCode]++
		if res.ContentLength > 0 {
			b.sizeTotal += int64(res.ContentLength)
		}
	}
	if b.boom.Duration == 0 {
		b.bar.Increment()
	}
}

// End finishes interface.
func (b *BasicInterface) End() {
	b.bar.Finish()
	b.total = time.Now().Sub(b.start)
	count := float64(b.histo.Count())
	b.rps = count / b.total.Seconds()
	b.average = b.avgTotal / count
	b.print()
}

func (b *BasicInterface) initProgressBar() {
	if b.boom.Duration > 0 {
		b.bar = pb.New(100)
		b.pct = 0
		ticker := time.NewTicker(b.boom.Duration / 100)
		go func() {
			for range ticker.C {
				if b.pct < 100 {
					b.pct += 1
					b.bar.Increment()
				}
			}
		}()
	} else {
		b.bar = pb.New(int(b.boom.N))
	}
	b.bar.BarStart = "Pl"
	b.bar.BarEnd = "!"
	b.bar.Empty = " "
	b.bar.Current = "a"
	b.bar.CurrentN = "a"
	b.bar.Start()
}

func (b *BasicInterface) print() {
	if b.histo.Count() > 0 {
		fmt.Printf("\nSummary:\n")
		fmt.Printf("  Total:\t%4.4f secs.\n", b.total.Seconds())
		fmt.Printf("  Slowest:\t%4.4f secs.\n", b.slowest)
		fmt.Printf("  Fastest:\t%4.4f secs.\n", b.fastest)
		fmt.Printf("  Average:\t%4.4f secs.\n", b.average)
		fmt.Printf("  Requests/sec:\t%4.4f\n", b.rps)
		if b.sizeTotal > 0 {
			fmt.Printf("  Total Data Received:\t%d bytes.\n", b.sizeTotal)
			fmt.Printf("  Response Size per Request:\t%d bytes.\n", b.sizeTotal/int64(b.histo.Count()))
		}
		b.printStatusCodes()
		b.printHistogram()
		b.printLatencies()
	}

	if len(b.errorDist) > 0 {
		b.printErrors()
	}
}

// Prints percentile latencies.
func (b *BasicInterface) printLatencies() {
	pctls := []int{10, 25, 50, 75, 90, 95, 99}
	fmt.Printf("\nLatency distribution:\n")
	cent := float64(100)
	for _, p := range pctls {
		q := b.histo.Quantile(float64(p) / cent)
		if q > 0 {
			fmt.Printf("  %v%% in %4.4f secs.\n", p, q)
		}
	}
}

func (b *BasicInterface) printHistogram() {
	fmt.Printf("\nResponse time histogram:\n")
	bins := b.histo.Bins()
	max := bins[0].Count
	for i := 1; i < len(bins); i++ {
		if bins[i].Count > max {
			max = bins[i].Count
		}
	}
	for i := 0; i < len(bins); i++ {
		// Normalize bar lengths.
		var barLen uint64
		if max > 0 {
			barLen = bins[i].Count * 40 / max
		}
		fmt.Printf("  %4.3f [%v]\t|%v\n", bins[i].Value, bins[i].Count, strings.Repeat(barChar, int(barLen)))
	}
}

// Prints status code distribution.
func (b *BasicInterface) printStatusCodes() {
	fmt.Printf("\nStatus code distribution:\n")
	for code, num := range b.statusCodeDist {
		fmt.Printf("  [%d]\t%d responses\n", code, num)
	}
}

func (b *BasicInterface) printErrors() {
	fmt.Printf("\nError distribution:\n")
	for err, num := range b.errorDist {
		fmt.Printf("  [%d]\t%s\n", num, err)
	}
}
<commit_msg>tweak error results<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package interfaces

import (
	"fmt"
	"strings"
	"time"

	"github.com/mercadolibre/pla/boomer"
	"github.com/sschepens/gohistogram"
	"github.com/sschepens/pb"
)

const (
	barChar = "∎"
)

// BasicInterface is Pla's default text-based terminal interface.
type BasicInterface struct {
	avgTotal float64
	fastest  float64
	slowest  float64
	average  float64
	rps      float64

	start time.Time
	total time.Duration

	errorDist      map[string]int
	statusCodeDist map[int]int
	sizeTotal      int64

	boom  *boomer.Boomer
	histo *gohistogram.NumericHistogram
	bar   *pb.ProgressBar
	pct   int
}

// NewBasicInterface instantiates a new BasicInterface.
func NewBasicInterface() *BasicInterface {
	return &BasicInterface{
		start:          time.Now(),
		statusCodeDist: make(map[int]int),
		errorDist:      make(map[string]int),
		histo:          gohistogram.NewHistogram(10),
	}
}

// Start initializes interface
func (b *BasicInterface) Start(boom *boomer.Boomer) {
	b.boom = boom
	b.initProgressBar()
}

// ProcessResult increments ProgressBar and keeps track of statistics.
func (b *BasicInterface) ProcessResult(res boomer.Result) {
	if res.Err != nil {
		b.errorDist[res.Err.Error()]++
	} else {
		sec := res.Duration.Seconds()
		if b.slowest == 0 || sec > b.slowest {
			b.slowest = sec
		}
		if b.fastest == 0 || b.fastest > sec {
			b.fastest = sec
		}
		b.histo.Add(res.Duration.Seconds())
		b.avgTotal += res.Duration.Seconds()
		b.statusCodeDist[res.StatusCode]++
		if res.ContentLength > 0 {
			b.sizeTotal += int64(res.ContentLength)
		}
	}
	if b.boom.Duration == 0 {
		b.bar.Increment()
	}
}

// End finishes interface.
func (b *BasicInterface) End() {
	b.bar.Finish()
	b.total = time.Now().Sub(b.start)
	count := float64(b.histo.Count())
	b.rps = count / b.total.Seconds()
	b.average = b.avgTotal / count
	b.print()
}

func (b *BasicInterface) initProgressBar() {
	if b.boom.Duration > 0 {
		b.bar = pb.New(100)
		b.pct = 0
		ticker := time.NewTicker(b.boom.Duration / 100)
		go func() {
			for range ticker.C {
				if b.pct < 100 {
					b.pct += 1
					b.bar.Increment()
				}
			}
		}()
	} else {
		b.bar = pb.New(int(b.boom.N))
	}
	b.bar.BarStart = "Pl"
	b.bar.BarEnd = "!"
	b.bar.Empty = " "
	b.bar.Current = "a"
	b.bar.CurrentN = "a"
	b.bar.Start()
}

func (b *BasicInterface) print() {
	if b.histo.Count() > 0 {
		fmt.Printf("\nSummary:\n")
		fmt.Printf("  Total:\t%4.4f secs.\n", b.total.Seconds())
		fmt.Printf("  Slowest:\t%4.4f secs.\n", b.slowest)
		fmt.Printf("  Fastest:\t%4.4f secs.\n", b.fastest)
		fmt.Printf("  Average:\t%4.4f secs.\n", b.average)
		fmt.Printf("  Requests/sec:\t%4.4f\n", b.rps)
		if b.sizeTotal > 0 {
			fmt.Printf("  Total Data Received:\t%d bytes.\n", b.sizeTotal)
			fmt.Printf("  Response Size per Request:\t%d bytes.\n", b.sizeTotal/int64(b.histo.Count()))
		}
		b.printStatusCodes()
	}

	if len(b.errorDist) > 0 {
		b.printErrors()
	}

	if b.histo.Count() > 0 {
		b.printHistogram()
		b.printLatencies()
	}
}

// Prints percentile latencies.
func (b *BasicInterface) printLatencies() {
	pctls := []int{10, 25, 50, 75, 90, 95, 99}
	fmt.Printf("\nLatency distribution:\n")
	cent := float64(100)
	for _, p := range pctls {
		q := b.histo.Quantile(float64(p) / cent)
		if q > 0 {
			fmt.Printf("  %v%% in %4.4f secs.\n", p, q)
		}
	}
}

func (b *BasicInterface) printHistogram() {
	fmt.Printf("\nResponse time histogram:\n")
	bins := b.histo.Bins()
	max := bins[0].Count
	for i := 1; i < len(bins); i++ {
		if bins[i].Count > max {
			max = bins[i].Count
		}
	}
	for i := 0; i < len(bins); i++ {
		// Normalize bar lengths.
		var barLen uint64
		if max > 0 {
			barLen = bins[i].Count * 40 / max
		}
		fmt.Printf("  %4.3f [%v]\t|%v\n", bins[i].Value, bins[i].Count, strings.Repeat(barChar, int(barLen)))
	}
}

// Prints status code distribution.
func (b *BasicInterface) printStatusCodes() {
	fmt.Printf("\nStatus code distribution:\n")
	for code, num := range b.statusCodeDist {
		fmt.Printf("  [%d]\t%d responses\n", code, num)
	}
}

func (b *BasicInterface) printErrors() {
	fmt.Printf("\nError distribution:\n")
	for err, num := range b.errorDist {
		fmt.Printf("  [%s]\t%d occurrences\n", err, num)
	}
}
<|endoftext|>


<commit_before>// list associated stations
package main

import (
	"flag"
	"fmt"
	"github.com/dim13/unifi"
	"log"
)

var (
	host    = flag.String("host", "unifi", "Controller hostname")
	user    = flag.String("user", "admin", "Controller username")
	pass    = flag.String("pass", "unifi", "Controller password")
	version = flag.Int("version", 2, "Controller base version")
	siteid  = flag.String("siteid", "default", "Site ID, UniFi v3 only")
)

func main() {
	flag.Parse()
	u, err := unifi.Login(*user, *pass, *host, *siteid, *version)
	if err != nil {
		log.Fatal("Login returned error: ", err)
	}
	defer u.Logout()

	aps, err := u.ApsMap()
	if err != nil {
		log.Fatal(err)
	}
	sta, err := u.Sta()
	if err != nil {
		log.Fatal(err)
	}

	for _, s := range sta {
		fmt.Printf("%24s%3s%12s%3d%5d%5d%5d%8s/%-3d%16s%4s\n",
			s.Name(), s.Radio, s.EssID, s.RoamCount, s.Signal, s.Noise, s.Rssi,
			aps[s.ApMac].Name, s.Channel, s.IP, aps[s.ApMac].Model)
	}
}
<commit_msg>Follow best practices<commit_after>// list associated stations
package main

import (
	"flag"
	"fmt"
	"log"

	"github.com/dim13/unifi"
)

var (
	host    = flag.String("host", "unifi", "Controller hostname")
	user    = flag.String("user", "admin", "Controller username")
	pass    = flag.String("pass", "unifi", "Controller password")
	version = flag.Int("version", 2, "Controller base version")
	siteid  = flag.String("siteid", "default", "Site ID, UniFi v3 only")
)

func main() {
	flag.Parse()
	u, err := unifi.Login(*user, *pass, *host, *siteid, *version)
	if err != nil {
		log.Fatal("Login returned error: ", err)
	}
	defer u.Logout()

	aps, err := u.ApsMap()
	if err != nil {
		log.Fatal(err)
	}
	sta, err := u.Sta()
	if err != nil {
		log.Fatal(err)
	}

	for _, s := range sta {
		fmt.Printf("%24s%3s%12s%3d%5d%5d%5d%8s/%-3d%16s%4s\n",
			s.Name(), s.Radio, s.EssID, s.RoamCount, s.Signal, s.Noise, s.Rssi,
			aps[s.ApMac].Name, s.Channel, s.IP, aps[s.ApMac].Model)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2015, Peter Mrekaj. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.txt file.

package lists

// MergeSorted merges int nodes from l and f sorted lists into the ordered list m.
// Note: when l or f contains different type from int then false is returned and
// merged list will contains some value(s) merged from l or f up to the different
// type.
func MergeSorted(l, f *List) (m *List, ok bool) {
	m = new(List)
	for l.Len() > 0 || f.Len() > 0 {
		vl, nl, okl := PopInt(l)
		if !okl {
			return m, false
		}
		vf, nf, okf := PopInt(f)
		if !okf {
			return m, false
		}

		ll, n := l, nl // The assumption is: vl <= vf.
		switch {
		case l.Len() == 0:
			ll, n = f, nf
		case f.Len() == 0:
			ll, n = l, nl
		case vl > vf:
			ll, n = f, nf
		}

		m.Insert(ll.Remove(n))
	}
	return m, true
}
<commit_msg>Simplify checking when pop-ed value isn't int<commit_after>// Copyright (c) 2015, Peter Mrekaj. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.txt file.

package lists

// MergeSorted merges int nodes from l and f sorted lists into the ordered list m.
// Note: when l or f contains different type from int then false is returned and
// merged list will contains some value(s) merged from l or f up to the different
// type.
func MergeSorted(l, f *List) (m *List, ok bool) {
	m = new(List)
	for l.Len() > 0 || f.Len() > 0 {
		vl, nl, okl := PopInt(l)
		vf, nf, okf := PopInt(f)
		if !okl || !okf {
			return m, false
		}

		ll, n := l, nl // The assumption is: vl <= vf.
		switch {
		case l.Len() == 0:
			ll, n = f, nf
		case f.Len() == 0:
			ll, n = l, nl
		case vl > vf:
			ll, n = f, nf
		}

		m.Insert(ll.Remove(n))
	}
	return m, true
}
<|endoftext|>


<commit_before>package action

import (
	"bytes"
	"context"
	"fmt"

	"github.com/gopasspw/gopass/internal/audit"
	"github.com/gopasspw/gopass/internal/editor"
	"github.com/gopasspw/gopass/internal/out"
	"github.com/gopasspw/gopass/internal/termio"
	"github.com/gopasspw/gopass/pkg/ctxutil"
	"github.com/gopasspw/gopass/pkg/gopass/secret"
	"github.com/gopasspw/gopass/pkg/gopass/secret/secparse"
	"github.com/gopasspw/gopass/pkg/pwgen"

	"github.com/urfave/cli/v2"
)

// Edit the content of a password file
func (s *Action) Edit(c *cli.Context) error {
	ctx := ctxutil.WithGlobalFlags(c)
	name := c.Args().First()
	if name == "" {
		return ExitError(ExitUsage, nil, "Usage: %s edit secret", s.Name)
	}

	return s.edit(ctx, c, name)
}

func (s *Action) edit(ctx context.Context, c *cli.Context, name string) error {
	ed := editor.Path(c)
	if err := editor.Check(ctx, ed); err != nil {
		out.Red(ctx, "Failed to check editor config: %s", err)
	}

	// get existing content or generate new one from a template
	name, content, changed, err := s.editGetContent(ctx, name, c.Bool("create"))
	if err != nil {
		return err
	}

	fromMIME := false
	if bytes.HasPrefix(content, []byte(secret.Ident)) {
		fromMIME = true
		content = bytes.TrimPrefix(content, []byte(secret.Ident+"\n"))
	}

	for {
		// invoke the editor to let the user edit the content
		nContent, err := editor.Invoke(ctx, ed, content)
		if err != nil {
			return ExitError(ExitUnknown, err, "failed to invoke editor: %s", err)
		}
		if fromMIME {
			nContent = append([]byte(secret.Ident+"\n"), nContent...)
			if _, err := secparse.Parse(nContent); err != nil {
				cont, err := termio.AskForBool(ctx, "ERROR: Can not parse Gopass native Secret. Retry?", true)
				if err != nil {
					return err
				}
				if !cont {
					return fmt.Errorf("malformed secret")
				}
				continue
			}
		}
		return s.editUpdate(ctx, name, content, nContent, changed, ed)
	}
}

func (s *Action) editUpdate(ctx context.Context, name string, content, nContent []byte, changed bool, ed string) error {
	// If content is equal, nothing changed, exiting
	if bytes.Equal(content, nContent) && !changed {
		return nil
	}

	nSec, err := secparse.Parse(nContent)
	if err != nil {
		out.Error(ctx, "WARNING: Invalid Secret: %s", err)
	}

	// if the secret has a password, we check it's strength
	if pw := nSec.Get("password"); pw != "" {
		audit.Single(ctx, pw)
	}

	// write result (back) to store
	if err := s.Store.Set(ctxutil.WithCommitMessage(ctx, fmt.Sprintf("Edited with %s", ed)), name, nSec); err != nil {
		return ExitError(ExitEncrypt, err, "failed to encrypt secret %s: %s", name, err)
	}
	return nil
}

func (s *Action) editGetContent(ctx context.Context, name string, create bool) (string, []byte, bool, error) {
	if !s.Store.Exists(ctx, name) {
		newName := ""
		// capture only the name of the selected secret
		cb := func(ctx context.Context, c *cli.Context, name string, recurse bool) error {
			newName = name
			return nil
		}
		if err := s.find(ctxutil.WithFuzzySearch(ctx, false), nil, name, cb); err == nil {
			name = newName
		}
	}

	// edit existing entry
	if s.Store.Exists(ctx, name) {
		sec, err := s.Store.Get(ctx, name)
		if err != nil {
			return name, nil, false, ExitError(ExitDecrypt, err, "failed to decrypt %s: %s", name, err)
		}
		return name, sec.Bytes(), false, nil
	}

	if !create {
		out.Yellow(ctx, "Entry %s not found. Creating new secret ...", name)
	}

	// load template if it exists
	if content, found := s.renderTemplate(ctx, name, []byte(pwgen.GeneratePassword(defaultLength, false))); found {
		return name, content, true, nil
	}

	// new entry, no template
	return name, nil, false, nil
}
<commit_msg>Preserve content across retries during edit (#1608)<commit_after>package action

import (
	"bytes"
	"context"
	"fmt"

	"github.com/gopasspw/gopass/internal/audit"
	"github.com/gopasspw/gopass/internal/editor"
	"github.com/gopasspw/gopass/internal/out"
	"github.com/gopasspw/gopass/internal/termio"
	"github.com/gopasspw/gopass/pkg/ctxutil"
	"github.com/gopasspw/gopass/pkg/gopass/secret"
	"github.com/gopasspw/gopass/pkg/gopass/secret/secparse"
	"github.com/gopasspw/gopass/pkg/pwgen"

	"github.com/urfave/cli/v2"
)

// Edit the content of a password file
func (s *Action) Edit(c *cli.Context) error {
	ctx := ctxutil.WithGlobalFlags(c)
	name := c.Args().First()
	if name == "" {
		return ExitError(ExitUsage, nil, "Usage: %s edit secret", s.Name)
	}

	return s.edit(ctx, c, name)
}

func (s *Action) edit(ctx context.Context, c *cli.Context, name string) error {
	ed := editor.Path(c)
	if err := editor.Check(ctx, ed); err != nil {
		out.Red(ctx, "Failed to check editor config: %s", err)
	}

	// get existing content or generate new one from a template
	name, content, changed, err := s.editGetContent(ctx, name, c.Bool("create"))
	if err != nil {
		return err
	}

	fromMIME := false
	if bytes.HasPrefix(content, []byte(secret.Ident)) {
		fromMIME = true
		content = bytes.TrimPrefix(content, []byte(secret.Ident+"\n"))
	}

	// preserve intermediate state across retries
	tmpContent := content
	for {
		// invoke the editor to let the user edit the content
		newContent, err := editor.Invoke(ctx, ed, tmpContent)
		if err != nil {
			return ExitError(ExitUnknown, err, "failed to invoke editor: %s", err)
		}
		// retain edited content for next try
		tmpContent = newContent
		if fromMIME {
			newContent = append([]byte(secret.Ident+"\n"), newContent...)
			if _, err := secparse.Parse(newContent); err != nil {
				out.Red(ctx, "ERROR: Cannot parse Gopass native Secret: %s", err)
				out.Yellow(ctx, "Hint: Does your Key-Value section end with a new line?")
				cont, err := termio.AskForBool(ctx, "Retry?", true)
				if err != nil {
					return err
				}
				if !cont {
					return fmt.Errorf("malformed secret")
				}
				continue
			}
		}
		return s.editUpdate(ctx, name, content, newContent, changed, ed)
	}
}

func (s *Action) editUpdate(ctx context.Context, name string, content, nContent []byte, changed bool, ed string) error {
	// If content is equal, nothing changed, exiting
	if bytes.Equal(content, nContent) && !changed {
		return nil
	}

	nSec, err := secparse.Parse(nContent)
	if err != nil {
		out.Error(ctx, "WARNING: Invalid Secret: %s", err)
	}

	// if the secret has a password, we check it's strength
	if pw := nSec.Get("password"); pw != "" {
		audit.Single(ctx, pw)
	}

	// write result (back) to store
	if err := s.Store.Set(ctxutil.WithCommitMessage(ctx, fmt.Sprintf("Edited with %s", ed)), name, nSec); err != nil {
		return ExitError(ExitEncrypt, err, "failed to encrypt secret %s: %s", name, err)
	}
	return nil
}

func (s *Action) editGetContent(ctx context.Context, name string, create bool) (string, []byte, bool, error) {
	if !s.Store.Exists(ctx, name) {
		newName := ""
		// capture only the name of the selected secret
		cb := func(ctx context.Context, c *cli.Context, name string, recurse bool) error {
			newName = name
			return nil
		}
		if err := s.find(ctxutil.WithFuzzySearch(ctx, false), nil, name, cb); err == nil {
			name = newName
		}
	}

	// edit existing entry
	if s.Store.Exists(ctx, name) {
		sec, err := s.Store.Get(ctx, name)
		if err != nil {
			return name, nil, false, ExitError(ExitDecrypt, err, "failed to decrypt %s: %s", name, err)
		}
		return name, sec.Bytes(), false, nil
	}

	if !create {
		out.Yellow(ctx, "Entry %s not found. Creating new secret ...", name)
	}

	// load template if it exists
	if content, found := s.renderTemplate(ctx, name, []byte(pwgen.GeneratePassword(defaultLength, false))); found {
		return name, content, true, nil
	}

	// new entry, no template
	return name, nil, false, nil
}
<|endoftext|>


<commit_before>// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package shader

import (
	"fmt"
	"go/ast"
	"go/token"

	"github.com/hajimehoshi/ebiten/internal/shaderir"
)

func (cs *compileState) parseStmt(block *block, stmt ast.Stmt, inParams []variable) ([]shaderir.Stmt, bool) {
	var stmts []shaderir.Stmt

	switch stmt := stmt.(type) {
	case *ast.AssignStmt:
		switch stmt.Tok {
		case token.DEFINE:
			if len(stmt.Lhs) != len(stmt.Rhs) && len(stmt.Rhs) != 1 {
				cs.addError(stmt.Pos(), fmt.Sprintf("single-value context and multiple-value context cannot be mixed"))
				return nil, false
			}

			ss, ok := cs.assign(block, stmt.Pos(), stmt.Lhs, stmt.Rhs, true)
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)
		case token.ASSIGN:
			// TODO: What about the statement `a,b = b,a?`
			if len(stmt.Lhs) != len(stmt.Rhs) && len(stmt.Rhs) != 1 {
				cs.addError(stmt.Pos(), fmt.Sprintf("single-value context and multiple-value context cannot be mixed"))
				return nil, false
			}
			ss, ok := cs.assign(block, stmt.Pos(), stmt.Lhs, stmt.Rhs, false)
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)
		case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN:
			var op shaderir.Op
			switch stmt.Tok {
			case token.ADD_ASSIGN:
				op = shaderir.Add
			case token.SUB_ASSIGN:
				op = shaderir.Sub
			case token.MUL_ASSIGN:
				op = shaderir.Mul
			case token.QUO_ASSIGN:
				op = shaderir.Div
			case token.REM_ASSIGN:
				op = shaderir.ModOp
			}

			rhs, _, ss, ok := cs.parseExpr(block, stmt.Rhs[0])
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)

			lhs, _, ss, ok := cs.parseExpr(block, stmt.Lhs[0])
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)

			stmts = append(stmts, shaderir.Stmt{
				Type: shaderir.Assign,
				Exprs: []shaderir.Expr{
					lhs[0],
					{
						Type: shaderir.Binary,
						Op:   op,
						Exprs: []shaderir.Expr{
							lhs[0],
							rhs[0],
						},
					},
				},
			})
		default:
			cs.addError(stmt.Pos(), fmt.Sprintf("unexpected token: %s", stmt.Tok))
		}
	case *ast.BlockStmt:
		b, ok := cs.parseBlock(block, stmt.List, nil, nil)
		if !ok {
			return nil, false
		}
		stmts = append(stmts, shaderir.Stmt{
			Type: shaderir.BlockStmt,
			Blocks: []shaderir.Block{
				b.ir,
			},
		})
	case *ast.DeclStmt:
		if !cs.parseDecl(block, stmt.Decl) {
			return nil, false
		}
	case *ast.IfStmt:
		// TODO: Parse stmt.Init

		exprs, ts, ss, ok := cs.parseExpr(block, stmt.Cond)
		if !ok {
			return nil, false
		}
		if len(ts) != 1 || ts[0].Main != shaderir.Bool {
			cs.addError(stmt.Pos(), fmt.Sprintf("if-condition must be bool but: %#v", ts))
			return nil, false
		}
		stmts = append(stmts, ss...)

		var bs []shaderir.Block
		b, ok := cs.parseBlock(block, stmt.Body.List, nil, nil)
		if !ok {
			return nil, false
		}
		bs = append(bs, b.ir)

		if stmt.Else != nil {
			switch s := stmt.Else.(type) {
			case *ast.BlockStmt:
				b, ok := cs.parseBlock(block, s.List, nil, nil)
				if !ok {
					return nil, false
				}
				bs = append(bs, b.ir)
			default:
				b, ok := cs.parseBlock(block, []ast.Stmt{s}, nil, nil)
				if !ok {
					return nil, false
				}
				bs = append(bs, b.ir)
			}
		}

		stmts = append(stmts, shaderir.Stmt{
			Type:   shaderir.If,
			Exprs:  exprs,
			Blocks: bs,
		})
	case *ast.ReturnStmt:
		for i, r := range stmt.Results {
			exprs, _, ss, ok := cs.parseExpr(block, r)
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)
			if len(exprs) == 0 {
				continue
			}
			if len(exprs) > 1 {
				cs.addError(r.Pos(), "multiple-context with return is not implemented yet")
				continue
			}
			stmts = append(stmts, shaderir.Stmt{
				Type: shaderir.Assign,
				Exprs: []shaderir.Expr{
					{
						Type:  shaderir.LocalVariable,
						Index: len(inParams) + i,
					},
					exprs[0],
				},
			})
		}
		stmts = append(stmts, shaderir.Stmt{
			Type: shaderir.Return,
		})
	case *ast.ExprStmt:
		exprs, _, ss, ok := cs.parseExpr(block, stmt.X)
		if !ok {
			return nil, false
		}
		stmts = append(stmts, ss...)

		for _, expr := range exprs {
			if expr.Type != shaderir.Call {
				continue
			}
			stmts = append(stmts, shaderir.Stmt{
				Type:  shaderir.ExprStmt,
				Exprs: []shaderir.Expr{expr},
			})
		}
	default:
		cs.addError(stmt.Pos(), fmt.Sprintf("unexpected statement: %#v", stmt))
		return nil, false
	}
	return stmts, true
}

func (cs *compileState) assign(block *block, pos token.Pos, lhs, rhs []ast.Expr, define bool) ([]shaderir.Stmt, bool) {
	var stmts []shaderir.Stmt
	var rhsExprs []shaderir.Expr
	var rhsTypes []shaderir.Type

	for i, e := range lhs {
		if len(lhs) == len(rhs) {
			// Prase RHS first for the order of the statements.
			r, origts, ss, ok := cs.parseExpr(block, rhs[i])
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)

			if define {
				v := variable{
					name: e.(*ast.Ident).Name,
				}
				ts, ok := cs.functionReturnTypes(block, rhs[i])
				if !ok {
					ts = origts
				}
				if len(ts) > 1 {
					cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed"))
					return nil, false
				}
				if len(ts) == 1 {
					v.typ = ts[0]
				}
				block.vars = append(block.vars, v)
			}

			if len(r) > 1 {
				cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed"))
				return nil, false
			}

			l, _, ss, ok := cs.parseExpr(block, lhs[i])
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)

			if r[0].Type == shaderir.NumberExpr {
				switch block.vars[l[0].Index].typ.Main {
				case shaderir.Int:
					r[0].ConstType = shaderir.ConstTypeInt
				case shaderir.Float:
					r[0].ConstType = shaderir.ConstTypeFloat
				}
			}

			stmts = append(stmts, shaderir.Stmt{
				Type:  shaderir.Assign,
				Exprs: []shaderir.Expr{l[0], r[0]},
			})
		} else {
			if i == 0 {
				var ss []shaderir.Stmt
				var ok bool
				rhsExprs, rhsTypes, ss, ok = cs.parseExpr(block, rhs[0])
				if !ok {
					return nil, false
				}
				if len(rhsExprs) != len(lhs) {
					cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed"))
				}
				stmts = append(stmts, ss...)
			}

			if define {
				v := variable{
					name: e.(*ast.Ident).Name,
				}
				v.typ = rhsTypes[i]
				block.vars = append(block.vars, v)
			}

			l, _, ss, ok := cs.parseExpr(block, lhs[i])
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)

			stmts = append(stmts, shaderir.Stmt{
				Type:  shaderir.Assign,
				Exprs: []shaderir.Expr{l[0], rhsExprs[i]},
			})
		}
	}
	return stmts, true
}
<commit_msg>shader: Better error message<commit_after>// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package shader

import (
	"fmt"
	"go/ast"
	"go/token"
	"strings"

	"github.com/hajimehoshi/ebiten/internal/shaderir"
)

func (cs *compileState) parseStmt(block *block, stmt ast.Stmt, inParams []variable) ([]shaderir.Stmt, bool) {
	var stmts []shaderir.Stmt

	switch stmt := stmt.(type) {
	case *ast.AssignStmt:
		switch stmt.Tok {
		case token.DEFINE:
			if len(stmt.Lhs) != len(stmt.Rhs) && len(stmt.Rhs) != 1 {
				cs.addError(stmt.Pos(), fmt.Sprintf("single-value context and multiple-value context cannot be mixed"))
				return nil, false
			}

			ss, ok := cs.assign(block, stmt.Pos(), stmt.Lhs, stmt.Rhs, true)
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)
		case token.ASSIGN:
			// TODO: What about the statement `a,b = b,a?`
			if len(stmt.Lhs) != len(stmt.Rhs) && len(stmt.Rhs) != 1 {
				cs.addError(stmt.Pos(), fmt.Sprintf("single-value context and multiple-value context cannot be mixed"))
				return nil, false
			}
			ss, ok := cs.assign(block, stmt.Pos(), stmt.Lhs, stmt.Rhs, false)
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)
		case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN:
			var op shaderir.Op
			switch stmt.Tok {
			case token.ADD_ASSIGN:
				op = shaderir.Add
			case token.SUB_ASSIGN:
				op = shaderir.Sub
			case token.MUL_ASSIGN:
				op = shaderir.Mul
			case token.QUO_ASSIGN:
				op = shaderir.Div
			case token.REM_ASSIGN:
				op = shaderir.ModOp
			}

			rhs, _, ss, ok := cs.parseExpr(block, stmt.Rhs[0])
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)

			lhs, _, ss, ok := cs.parseExpr(block, stmt.Lhs[0])
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)

			stmts = append(stmts, shaderir.Stmt{
				Type: shaderir.Assign,
				Exprs: []shaderir.Expr{
					lhs[0],
					{
						Type: shaderir.Binary,
						Op:   op,
						Exprs: []shaderir.Expr{
							lhs[0],
							rhs[0],
						},
					},
				},
			})
		default:
			cs.addError(stmt.Pos(), fmt.Sprintf("unexpected token: %s", stmt.Tok))
		}
	case *ast.BlockStmt:
		b, ok := cs.parseBlock(block, stmt.List, nil, nil)
		if !ok {
			return nil, false
		}
		stmts = append(stmts, shaderir.Stmt{
			Type: shaderir.BlockStmt,
			Blocks: []shaderir.Block{
				b.ir,
			},
		})
	case *ast.DeclStmt:
		if !cs.parseDecl(block, stmt.Decl) {
			return nil, false
		}
	case *ast.IfStmt:
		// TODO: Parse stmt.Init

		exprs, ts, ss, ok := cs.parseExpr(block, stmt.Cond)
		if !ok {
			return nil, false
		}
		if len(ts) != 1 || ts[0].Main != shaderir.Bool {
			var tss []string
			for _, t := range ts {
				tss = append(tss, t.String())
			}
			cs.addError(stmt.Pos(), fmt.Sprintf("if-condition must be bool but: %s", strings.Join(tss, ", ")))
			return nil, false
		}
		stmts = append(stmts, ss...)

		var bs []shaderir.Block
		b, ok := cs.parseBlock(block, stmt.Body.List, nil, nil)
		if !ok {
			return nil, false
		}
		bs = append(bs, b.ir)

		if stmt.Else != nil {
			switch s := stmt.Else.(type) {
			case *ast.BlockStmt:
				b, ok := cs.parseBlock(block, s.List, nil, nil)
				if !ok {
					return nil, false
				}
				bs = append(bs, b.ir)
			default:
				b, ok := cs.parseBlock(block, []ast.Stmt{s}, nil, nil)
				if !ok {
					return nil, false
				}
				bs = append(bs, b.ir)
			}
		}

		stmts = append(stmts, shaderir.Stmt{
			Type:   shaderir.If,
			Exprs:  exprs,
			Blocks: bs,
		})
	case *ast.ReturnStmt:
		for i, r := range stmt.Results {
			exprs, _, ss, ok := cs.parseExpr(block, r)
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)
			if len(exprs) == 0 {
				continue
			}
			if len(exprs) > 1 {
				cs.addError(r.Pos(), "multiple-context with return is not implemented yet")
				continue
			}
			stmts = append(stmts, shaderir.Stmt{
				Type: shaderir.Assign,
				Exprs: []shaderir.Expr{
					{
						Type:  shaderir.LocalVariable,
						Index: len(inParams) + i,
					},
					exprs[0],
				},
			})
		}
		stmts = append(stmts, shaderir.Stmt{
			Type: shaderir.Return,
		})
	case *ast.ExprStmt:
		exprs, _, ss, ok := cs.parseExpr(block, stmt.X)
		if !ok {
			return nil, false
		}
		stmts = append(stmts, ss...)

		for _, expr := range exprs {
			if expr.Type != shaderir.Call {
				continue
			}
			stmts = append(stmts, shaderir.Stmt{
				Type:  shaderir.ExprStmt,
				Exprs: []shaderir.Expr{expr},
			})
		}
	default:
		cs.addError(stmt.Pos(), fmt.Sprintf("unexpected statement: %#v", stmt))
		return nil, false
	}
	return stmts, true
}

func (cs *compileState) assign(block *block, pos token.Pos, lhs, rhs []ast.Expr, define bool) ([]shaderir.Stmt, bool) {
	var stmts []shaderir.Stmt
	var rhsExprs []shaderir.Expr
	var rhsTypes []shaderir.Type

	for i, e := range lhs {
		if len(lhs) == len(rhs) {
			// Prase RHS first for the order of the statements.
			r, origts, ss, ok := cs.parseExpr(block, rhs[i])
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)

			if define {
				v := variable{
					name: e.(*ast.Ident).Name,
				}
				ts, ok := cs.functionReturnTypes(block, rhs[i])
				if !ok {
					ts = origts
				}
				if len(ts) > 1 {
					cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed"))
					return nil, false
				}
				if len(ts) == 1 {
					v.typ = ts[0]
				}
				block.vars = append(block.vars, v)
			}

			if len(r) > 1 {
				cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed"))
				return nil, false
			}

			l, _, ss, ok := cs.parseExpr(block, lhs[i])
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)

			if r[0].Type == shaderir.NumberExpr {
				switch block.vars[l[0].Index].typ.Main {
				case shaderir.Int:
					r[0].ConstType = shaderir.ConstTypeInt
				case shaderir.Float:
					r[0].ConstType = shaderir.ConstTypeFloat
				}
			}

			stmts = append(stmts, shaderir.Stmt{
				Type:  shaderir.Assign,
				Exprs: []shaderir.Expr{l[0], r[0]},
			})
		} else {
			if i == 0 {
				var ss []shaderir.Stmt
				var ok bool
				rhsExprs, rhsTypes, ss, ok = cs.parseExpr(block, rhs[0])
				if !ok {
					return nil, false
				}
				if len(rhsExprs) != len(lhs) {
					cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed"))
				}
				stmts = append(stmts, ss...)
			}

			if define {
				v := variable{
					name: e.(*ast.Ident).Name,
				}
				v.typ = rhsTypes[i]
				block.vars = append(block.vars, v)
			}

			l, _, ss, ok := cs.parseExpr(block, lhs[i])
			if !ok {
				return nil, false
			}
			stmts = append(stmts, ss...)

			stmts = append(stmts, shaderir.Stmt{
				Type:  shaderir.Assign,
				Exprs: []shaderir.Expr{l[0], rhsExprs[i]},
			})
		}
	}
	return stmts, true
}
<|endoftext|>


<commit_before>// Copyright 2019 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package testutil

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

// TestTempDir creates a temporary directory for use during tests. It returns
// the pathname, and a cleanup function.
func TestTempDir(tb testing.TB) (string, func()) {
	tb.Helper()
	name, err := ioutil.TempDir("", "mtail-test")
	if err != nil {
		tb.Fatal(err)
	}
	return name, func() {
		if err := os.RemoveAll(name); err != nil {
			tb.Fatalf("os.RemoveAll(%s): %s", name, err)
		}
	}
}

// TestOpenFile creates a new file called name and returns the opened file.
func TestOpenFile(tb testing.TB, name string) *os.File {
	tb.Helper()
	f, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600)
	if err != nil {
		tb.Fatal(err)
	}
	return f
}

// OpenLogFile creates a new file that emulates being a log.
func OpenLogFile(tb testing.TB, name string) *os.File {
	tb.Helper()
	f, err := os.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_APPEND, 0600)
	if err != nil {
		tb.Fatal(err)
	}
	return f
}

// TestChdir changes current working directory, and returns a cleanup function
// to return to the previous directory.
func TestChdir(tb testing.TB, dir string) func() {
	tb.Helper()
	cwd, err := os.Getwd()
	if err != nil {
		tb.Fatal(err)
	}
	err = os.Chdir(dir)
	if err != nil {
		tb.Fatal(err)
	}
	return func() {
		err := os.Chdir(cwd)
		if err != nil {
			tb.Fatal(err)
		}
	}
}
<commit_msg>In test, open files for O_WRONLY instead of O_RDWR.<commit_after>// Copyright 2019 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package testutil

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

// TestTempDir creates a temporary directory for use during tests. It returns
// the pathname, and a cleanup function.
func TestTempDir(tb testing.TB) (string, func()) {
	tb.Helper()
	name, err := ioutil.TempDir("", "mtail-test")
	if err != nil {
		tb.Fatal(err)
	}
	return name, func() {
		if err := os.RemoveAll(name); err != nil {
			tb.Fatalf("os.RemoveAll(%s): %s", name, err)
		}
	}
}

// TestOpenFile creates a new file called name and returns the opened file.
func TestOpenFile(tb testing.TB, name string) *os.File {
	tb.Helper()
	f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
	if err != nil {
		tb.Fatal(err)
	}
	return f
}

// OpenLogFile creates a new file that emulates being a log.
func OpenLogFile(tb testing.TB, name string) *os.File {
	tb.Helper()
	f, err := os.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_APPEND, 0600)
	if err != nil {
		tb.Fatal(err)
	}
	return f
}

// TestChdir changes current working directory, and returns a cleanup function
// to return to the previous directory.
func TestChdir(tb testing.TB, dir string) func() {
	tb.Helper()
	cwd, err := os.Getwd()
	if err != nil {
		tb.Fatal(err)
	}
	err = os.Chdir(dir)
	if err != nil {
		tb.Fatal(err)
	}
	return func() {
		err := os.Chdir(cwd)
		if err != nil {
			tb.Fatal(err)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2011 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oglemock

import (
	"fmt"
	"github.com/jacobsa/oglematchers"
	"reflect"
)

// InternalExpectation is exported for purposes of testing only. You should not
// touch it.
//
// InternalExpectation represents an expectation for zero or more calls to a
// mock method, and a set of actions to be taken when those calls are received.
type InternalExpectation struct {
	// The signature of the method to which this expectation is bound, for
	// checking action types.
	methodSignature reflect.Type

	// An error reporter to use for reporting errors in the way that expectations
	// are set.
	errorReporter ErrorReporter

	// Matchers that the arguments to the mock method must satisfy in order to
	// match this expectation.
	ArgMatchers []oglematchers.Matcher

	// The name of the file in which this expectation was expressed.
	FileName string

	// The line number at which this expectation was expressed.
	LineNumber int

	// The number of times this expectation should be matched, as explicitly
	// listed by the user. If there was no explicit number expressed, this is -1.
	ExpectedNumMatches int

	// Actions to be taken for the first N calls, one per call in order, where N
	// is the length of this slice.
	OneTimeActions []Action

	// An action to be taken when the one-time actions have expired, or nil if
	// there is no such action.
	FallbackAction Action

	// The number of times this expectation has been matched so far.
	NumMatches uint
}

// InternalNewExpectation is exported for purposes of testing only. You should
// not touch it.
func InternalNewExpectation(
	reporter ErrorReporter,
	methodSignature reflect.Type,
	args []interface{},
	fileName string,
	lineNumber int) *InternalExpectation {
	result := &InternalExpectation{}

	// Store fields that can be stored directly.
	result.methodSignature = methodSignature
	result.FileName = fileName
	result.LineNumber = lineNumber

	// Set up defaults.
	result.ExpectedNumMatches = -1
	result.OneTimeActions = make([]Action, 0)

	// Set up the ArgMatchers slice, using Equals(x) for each x that is not a
	// matcher itself.
	result.ArgMatchers = make([]oglematchers.Matcher, len(args))
	for i, x := range args {
		if matcher, ok := x.(oglematchers.Matcher); ok {
			result.ArgMatchers[i] = matcher
		} else {
			result.ArgMatchers[i] = oglematchers.Equals(x)
		}
	}

	return result
}

func (e *InternalExpectation) Times(n uint) Expectation {
	// It is illegal to call this more than once.
	if e.ExpectedNumMatches != -1 {
		panic("Times called more than once.")
	}

	// It is illegal to call this after any actions are configured.
	if len(e.OneTimeActions) != 0 {
		panic("Times called after WillOnce.")
	}

	if e.FallbackAction != nil {
		panic("Times called after WillRepeatedly.")
	}

	// Make sure the number is reasonable (and will fit in an int).
	if n > 1000 {
		panic("Expectation.Times: N must be at most 1000")
	}

	e.ExpectedNumMatches = int(n)
	return e
}

func (e *InternalExpectation) WillOnce(a Action) Expectation {
	// It is illegal to call this after WillRepeatedly.
	if e.FallbackAction != nil {
		panic("WillOnce called after WillRepeatedly.")
	}

	// Make sure the action is okay with the mock method's signature.
	if err := a.CheckType(e.methodSignature); err != nil {
		panic(fmt.Sprintf("WillOnce given invalid action: %v", err))
	}

	// Store the action.
	e.OneTimeActions = append(e.OneTimeActions, a)

	return e
}

func (e *InternalExpectation) WillRepeatedly(a Action) Expectation {
	// It is illegal to call this twice.
	if e.FallbackAction != nil {
		panic("WillRepeatedly called more than once.")
	}

	// Make sure the action is okay with the mock method's signature.
	if err := a.CheckType(e.methodSignature); err != nil {
		panic(fmt.Sprintf("WillRepeatedly given invalid action: %v", err))
	}

	// Store the action.
	e.FallbackAction = a

	return e
}
<commit_msg>Removed panics from InternalExpectation.<commit_after>// Copyright 2011 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oglemock

import (
	"errors"
	"fmt"
	"github.com/jacobsa/oglematchers"
	"reflect"
)

// InternalExpectation is exported for purposes of testing only. You should not
// touch it.
//
// InternalExpectation represents an expectation for zero or more calls to a
// mock method, and a set of actions to be taken when those calls are received.
type InternalExpectation struct {
	// The signature of the method to which this expectation is bound, for
	// checking action types.
	methodSignature reflect.Type

	// An error reporter to use for reporting errors in the way that expectations
	// are set.
	errorReporter ErrorReporter

	// Matchers that the arguments to the mock method must satisfy in order to
	// match this expectation.
	ArgMatchers []oglematchers.Matcher

	// The name of the file in which this expectation was expressed.
	FileName string

	// The line number at which this expectation was expressed.
	LineNumber int

	// The number of times this expectation should be matched, as explicitly
	// listed by the user. If there was no explicit number expressed, this is -1.
	ExpectedNumMatches int

	// Actions to be taken for the first N calls, one per call in order, where N
	// is the length of this slice.
	OneTimeActions []Action

	// An action to be taken when the one-time actions have expired, or nil if
	// there is no such action.
	FallbackAction Action

	// The number of times this expectation has been matched so far.
	NumMatches uint
}

// InternalNewExpectation is exported for purposes of testing only. You should
// not touch it.
func InternalNewExpectation(
	reporter ErrorReporter,
	methodSignature reflect.Type,
	args []interface{},
	fileName string,
	lineNumber int) *InternalExpectation {
	result := &InternalExpectation{}

	// Store fields that can be stored directly.
	result.methodSignature = methodSignature
	result.errorReporter = reporter
	result.FileName = fileName
	result.LineNumber = lineNumber

	// Set up defaults.
	result.ExpectedNumMatches = -1
	result.OneTimeActions = make([]Action, 0)

	// Set up the ArgMatchers slice, using Equals(x) for each x that is not a
	// matcher itself.
	result.ArgMatchers = make([]oglematchers.Matcher, len(args))
	for i, x := range args {
		if matcher, ok := x.(oglematchers.Matcher); ok {
			result.ArgMatchers[i] = matcher
		} else {
			result.ArgMatchers[i] = oglematchers.Equals(x)
		}
	}

	return result
}

func (e *InternalExpectation) Times(n uint) Expectation {
	// It is illegal to call this more than once.
	if e.ExpectedNumMatches != -1 {
		e.reportFatalError("Times called more than once.")
		return nil
	}

	// It is illegal to call this after any actions are configured.
	if len(e.OneTimeActions) != 0 {
		e.reportFatalError("Times called after WillOnce.")
		return nil
	}

	if e.FallbackAction != nil {
		e.reportFatalError("Times called after WillRepeatedly.")
		return nil
	}

	// Make sure the number is reasonable (and will fit in an int).
	if n > 1000 {
		e.reportFatalError("Expectation.Times: N must be at most 1000")
		return nil
	}

	e.ExpectedNumMatches = int(n)
	return e
}

func (e *InternalExpectation) WillOnce(a Action) Expectation {
	// It is illegal to call this after WillRepeatedly.
	if e.FallbackAction != nil {
		e.reportFatalError("WillOnce called after WillRepeatedly.")
		return nil
	}

	// Make sure the action is okay with the mock method's signature.
	if err := a.CheckType(e.methodSignature); err != nil {
		e.reportFatalError(fmt.Sprintf("WillOnce given invalid action: %v", err))
		return nil
	}

	// Store the action.
	e.OneTimeActions = append(e.OneTimeActions, a)

	return e
}

func (e *InternalExpectation) WillRepeatedly(a Action) Expectation {
	// It is illegal to call this twice.
	if e.FallbackAction != nil {
		e.reportFatalError("WillRepeatedly called more than once.")
		return nil
	}

	// Make sure the action is okay with the mock method's signature.
	if err := a.CheckType(e.methodSignature); err != nil {
		e.reportFatalError(fmt.Sprintf("WillRepeatedly given invalid action: %v", err))
		return nil
	}

	// Store the action.
	e.FallbackAction = a

	return e
}

func (e *InternalExpectation) reportFatalError(errorText string) {
	e.errorReporter.ReportFatalError(e.FileName, e.LineNumber, errors.New(errorText))
}
<|endoftext|>


<commit_before>package clio

import (
	"net/http"
	"regexp"
	"strings"

	"github.com/davidmz/mustbe"
	"github.com/juju/errors"
	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

var urlLikeRe = regexp.MustCompile(`^(https?|ftp)://`)

func deHTML(text string) (result string, links []string) {
	var (
		inAnchor bool
		aText    string
		aTitle   string
		aHref    string
	)
	z := html.NewTokenizer(strings.NewReader(text))
	for {
		tt := z.Next()
		switch tt {

		case html.ErrorToken:
			return

		case html.StartTagToken:
			t := z.Token()
			if t.DataAtom != atom.A {
				mustbe.OK(errors.Errorf("unexpected HTML tag %q", t.DataAtom))
			}
			inAnchor = true
			aText = ""
			aTitle = ""
			aHref = ""
			for _, a := range t.Attr {
				switch a.Key {
				case "title":
					aTitle = a.Val
				case "href":
					aHref = a.Val
				}
			}

		case html.TextToken:
			t := z.Token()
			if inAnchor {
				aText += t.Data
			} else {
				result += t.Data
			}

		case html.EndTagToken:
			inAnchor = false
			if !urlLikeRe.MatchString(aText) {
				result += aText
			} else if aTitle != "" {
				links = append(links, aTitle)
				result += aTitle
			} else {
				url := unshorten(aHref)
				links = append(links, url)
				result += url
			}
		}
	}
}

func unshorten(u string) string {
	isShort := strings.HasPrefix(u, "http://t.co/")
	if !isShort {
		return u
	}
	resp, err := http.Get(u) // not http.Head because of https://github.com/golang/go/issues/19895
	if err != nil {
		return u
	}
	resp.Body.Close()
	return resp.Request.URL.String()
}
<commit_msg>Add more shortener services to unshortener<commit_after>package clio

import (
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"

	"github.com/davidmz/mustbe"
	"github.com/juju/errors"
	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

var urlLikeRe = regexp.MustCompile(`^(https?|ftp)://`)

func deHTML(text string) (result string, links []string) {
	var (
		inAnchor bool
		aText    string
		aTitle   string
		aHref    string
	)
	z := html.NewTokenizer(strings.NewReader(text))
	for {
		tt := z.Next()
		switch tt {

		case html.ErrorToken:
			return

		case html.StartTagToken:
			t := z.Token()
			if t.DataAtom != atom.A {
				mustbe.OK(errors.Errorf("unexpected HTML tag %q", t.DataAtom))
			}
			inAnchor = true
			aText = ""
			aTitle = ""
			aHref = ""
			for _, a := range t.Attr {
				switch a.Key {
				case "title":
					aTitle = a.Val
				case "href":
					aHref = a.Val
				}
			}

		case html.TextToken:
			t := z.Token()
			if inAnchor {
				aText += t.Data
			} else {
				result += t.Data
			}

		case html.EndTagToken:
			inAnchor = false
			if !urlLikeRe.MatchString(aText) {
				result += aText
			} else if aTitle != "" {
				links = append(links, aTitle)
				result += aTitle
			} else {
				url := unshorten(aHref)
				links = append(links, url)
				result += url
			}
		}
	}
}

var shortDomains = []string{
	"t.co",
	"bit.ly",
	"bitly.com",
	"bitly.is",
	"j.mp",
	"goo.gl",
	"tinyurl.com",
	"ow.ly",
}

func unshorten(u string) string {
	pURL, err := url.Parse(u)
	if err != nil {
		return u
	}
	isShort := false
	for _, domain := range shortDomains {
		if pURL.Hostname() == domain {
			isShort = true
			break
		}
	}
	if !isShort {
		return u
	}

	// turn off log because of https://github.com/golang/go/issues/19895
	log.SetOutput(ioutil.Discard)
	resp, err := http.Head(u)
	log.SetOutput(os.Stderr)
	if err != nil {
		return u
	}
	resp.Body.Close()
	return resp.Request.URL.String()
}
<|endoftext|>


<commit_before>// Copyright 2017 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package clock manages game timers.
package clock

import (
	"sync"
	"time"
)

var (
	lastNow int64

	// lastSystemTime is the last system time in the previous Update.
	// lastSystemTime indicates the logical time in the game, so this can be bigger than the curren time.
	lastSystemTime int64

	currentFPS  float64
	currentTPS  float64
	lastUpdated int64
	fpsCount    = 0
	tpsCount    = 0

	m sync.Mutex
)

func init() {
	n := now()
	lastNow = n
	lastSystemTime = n
	lastUpdated = n
}

func CurrentFPS() float64 {
	m.Lock()
	v := currentFPS
	m.Unlock()
	return v
}

func CurrentTPS() float64 {
	m.Lock()
	v := currentTPS
	m.Unlock()
	return v
}

func max(a, b int64) int64 {
	if a < b {
		return b
	}
	return a
}

func calcCountFromTPS(tps int64, now int64) int {
	if tps == 0 {
		return 0
	}
	if tps < 0 {
		panic("clock: tps must >= 0")
	}

	diff := now - lastSystemTime
	if diff < 0 {
		return 0
	}

	count := 0
	syncWithSystemClock := false

	// Detect whether the previous time is too old.
	// Use either 5 ticks or 5/60 sec in the case when TPS is too big like 300 (#1444).
	if diff > max(int64(time.Second)*5/tps, int64(time.Second)*5/60) {
		// The previous time is too old.
		// Let's force to sync the game time with the system clock.
		syncWithSystemClock = true
	} else {
		count = int(diff * tps / int64(time.Second))
	}

	// Stabilize the count.
	// Without this adjustment, count can be unstable like 0, 2, 0, 2, ...
	// TODO: Brush up this logic so that this will work with any FPS. Now this works only when FPS = TPS.
	if count == 0 && (int64(time.Second)/tps/2) < diff {
		count = 1
	}
	if count == 2 && (int64(time.Second)/tps*3/2) > diff {
		count = 1
	}

	if syncWithSystemClock {
		lastSystemTime = now
	} else {
		lastSystemTime += int64(count) * int64(time.Second) / tps
	}

	return count
}

func updateFPSAndTPS(now int64, count int) {
	fpsCount++
	tpsCount += count
	if now < lastUpdated {
		panic("clock: lastUpdated must be older than now")
	}
	if time.Second > time.Duration(now-lastUpdated) {
		return
	}
	currentFPS = float64(fpsCount) * float64(time.Second) / float64(now-lastUpdated)
	currentTPS = float64(tpsCount) * float64(time.Second) / float64(now-lastUpdated)
	lastUpdated = now
	fpsCount = 0
	tpsCount = 0
}

const SyncWithFPS = -1

// Update updates the inner clock state and returns an integer value
// indicating how many times the game should update based on given tps.
// tps represents TPS (ticks per second).
// If tps is SyncWithFPS, Update always returns 1.
// If tps <= 0 and not SyncWithFPS, Update always returns 0.
//
// Update is expected to be called per frame.
func Update(tps int) int {
	m.Lock()
	defer m.Unlock()

	n := now()
	if lastNow > n {
		// This ensures that now() must be monotonic (#875).
		panic("clock: lastNow must be older than n")
	}
	lastNow = n

	c := 0
	if tps == SyncWithFPS {
		c = 1
	} else if tps > 0 {
		c = calcCountFromTPS(int64(tps), n)
	}
	updateFPSAndTPS(n, c)

	return c
}
<commit_msg>internal/clock: force to sync the timer when TPS is updated<commit_after>// Copyright 2017 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package clock manages game timers.
package clock

import (
	"sync"
	"time"
)

var (
	lastNow int64

	// lastSystemTime is the last system time in the previous Update.
	// lastSystemTime indicates the logical time in the game, so this can be bigger than the curren time.
	lastSystemTime int64

	currentFPS  float64
	currentTPS  float64
	prevTPS     int64
	lastUpdated int64
	fpsCount    = 0
	tpsCount    = 0

	m sync.Mutex
)

func init() {
	n := now()
	lastNow = n
	lastSystemTime = n
	lastUpdated = n
}

func CurrentFPS() float64 {
	m.Lock()
	v := currentFPS
	m.Unlock()
	return v
}

func CurrentTPS() float64 {
	m.Lock()
	v := currentTPS
	m.Unlock()
	return v
}

func max(a, b int64) int64 {
	if a < b {
		return b
	}
	return a
}

func calcCountFromTPS(tps int64, now int64) int {
	if tps == 0 {
		return 0
	}
	if tps < 0 {
		panic("clock: tps must >= 0")
	}

	diff := now - lastSystemTime
	if diff < 0 {
		return 0
	}

	count := 0
	syncWithSystemClock := false

	// Detect whether the previous time is too old.
	// Use either 5 ticks or 5/60 sec in the case when TPS is too big like 300 (#1444).
	if diff > max(int64(time.Second)*5/tps, int64(time.Second)*5/60) || prevTPS != tps {
		// The previous time is too old.
		// Let's force to sync the game time with the system clock.
		syncWithSystemClock = true
	} else {
		count = int(diff * tps / int64(time.Second))
	}
	prevTPS = tps

	// Stabilize the count.
	// Without this adjustment, count can be unstable like 0, 2, 0, 2, ...
	// TODO: Brush up this logic so that this will work with any FPS. Now this works only when FPS = TPS.
	if count == 0 && (int64(time.Second)/tps/2) < diff {
		count = 1
	}
	if count == 2 && (int64(time.Second)/tps*3/2) > diff {
		count = 1
	}

	if syncWithSystemClock {
		lastSystemTime = now
	} else {
		lastSystemTime += int64(count) * int64(time.Second) / tps
	}

	return count
}

func updateFPSAndTPS(now int64, count int) {
	fpsCount++
	tpsCount += count
	if now < lastUpdated {
		panic("clock: lastUpdated must be older than now")
	}
	if time.Second > time.Duration(now-lastUpdated) {
		return
	}
	currentFPS = float64(fpsCount) * float64(time.Second) / float64(now-lastUpdated)
	currentTPS = float64(tpsCount) * float64(time.Second) / float64(now-lastUpdated)
	lastUpdated = now
	fpsCount = 0
	tpsCount = 0
}

const SyncWithFPS = -1

// Update updates the inner clock state and returns an integer value
// indicating how many times the game should update based on given tps.
// tps represents TPS (ticks per second).
// If tps is SyncWithFPS, Update always returns 1.
// If tps <= 0 and not SyncWithFPS, Update always returns 0.
//
// Update is expected to be called per frame.
func Update(tps int) int {
	m.Lock()
	defer m.Unlock()

	n := now()
	if lastNow > n {
		// This ensures that now() must be monotonic (#875).
		panic("clock: lastNow must be older than n")
	}
	lastNow = n

	c := 0
	if tps == SyncWithFPS {
		c = 1
	} else if tps > 0 {
		c = calcCountFromTPS(int64(tps), n)
	}
	updateFPSAndTPS(n, c)

	return c
}
<|endoftext|>