<commit_before>/* Copyright 2020 Google Inc.

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

    https://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 os

import (
	"bytes"
	"context"
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"reflect"
	"testing"
	"time"

	"github.com/google/gnxi/gnoi/os/pb"
	"google.golang.org/grpc"
)

type activateRPC func(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error)
type verifyRPC func(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error)

type mockClient struct {
	pb.OSClient
	activate      activateRPC
	verify        verifyRPC
	installClient pb.OS_InstallClient
}

type installRequestMap struct {
	req  *pb.InstallRequest
	resp *pb.InstallResponse
}

type mockInstallClient struct {
	pb.OS_InstallClient
	reqMap  []*installRequestMap
	i       int
	recv    chan int
	recvErr chan *pb.InstallResponse_InstallError
}

func (c *mockInstallClient) Send(req *pb.InstallRequest) error {
	if c.i < len(c.reqMap) {
		if reflect.TypeOf(req.Request) == reflect.TypeOf(c.reqMap[c.i].req.Request) {
			c.recv <- c.i
		} else {
			c.recvErr <- &pb.InstallResponse_InstallError{
				InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Invalid command"},
			}
		}
		c.i++
	}
	return nil
}

func (c *mockInstallClient) Recv() (*pb.InstallResponse, error) {
	select {
	case i := <-c.recv:
		res := c.reqMap[i].resp
		if res == nil {
			<-time.After(200 * time.Millisecond)
		}
		return res, nil
	case err := <-c.recvErr:
		return &pb.InstallResponse{Response: err}, nil
	}
}

func (c *mockInstallClient) CloseSend() error {
	return nil
}

func (c *mockClient) Activate(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
	return c.activate(ctx, in, opts...)
}

func (c *mockClient) Verify(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error) {
	return c.verify(ctx, in, opts...)
}

func (c *mockClient) Install(ctx context.Context, opts ...grpc.CallOption) (pb.OS_InstallClient, error) {
	return c.installClient, nil
}

func activateErrorRPC(errType pb.ActivateError_Type, detail string) activateRPC {
	return func(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
		return &pb.ActivateResponse{
			Response: &pb.ActivateResponse_ActivateError{
				ActivateError: &pb.ActivateError{Type: errType, Detail: detail},
			}}, nil
	}
}

func activateSuccessRPC(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
	return &pb.ActivateResponse{
		Response: &pb.ActivateResponse_ActivateOk{}}, nil
}

func readBytes(num int) func(string) (io.ReaderAt, uint64, func() error, error) {
	b := make([]byte, num)
	rand.Read(b)
	return func(_ string) (io.ReaderAt, uint64, func() error, error) {
		return bytes.NewReader(b), uint64(num), func() error { return nil }, nil
	}
}

func TestInstall(t *testing.T) {
	installTests := []struct {
		name    string
		reqMap  []*installRequestMap
		reader  func(string) (io.ReaderAt, uint64, func() error, error)
		err     error
		timeout time.Duration
	}{
		{
			"Already validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes(0),
			nil,
			100 * time.Millisecond,
		},
		{
			"File size of one chunk then validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: chunkSize}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferEnd{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes(chunkSize),
			nil,
			100 * time.Millisecond,
		},
		{
			"File size of two chunks + 1 then validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: chunkSize}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: (chunkSize * 2)}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: (chunkSize * 2) + 1}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferEnd{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes((chunkSize * 2) + 1),
			nil,
			100 * time.Millisecond,
		},
		{
			"File size of one chunk but INCOMPATIBLE InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_INCOMPATIBLE}}},
				},
			},
			readBytes(chunkSize),
			errors.New("InstallError occured: INCOMPATIBLE"),
			100 * time.Millisecond,
		},
		{
			"TransferRequest but INCOMPATIBLE InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_INCOMPATIBLE}}},
				},
			},
			readBytes(0),
			errors.New("InstallError occured: INCOMPATIBLE"),
			100 * time.Millisecond,
		},
		{
			"TransferRequest but Unspecified InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Unspecified"}}},
				},
			},
			readBytes(0),
			errors.New("Unspecified InstallError error: Unspecified"),
			100 * time.Millisecond,
		},
		{
			"File size of two chunks but Unspecified InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Unspecified"}}},
				},
			},
			readBytes(chunkSize * 2),
			errors.New("Unspecified InstallError error: Unspecified"),
			100 * time.Millisecond,
		},
		{
			"File size of one chunk then timout",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: chunkSize}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferEnd{}},
					nil,
				},
			},
			readBytes(chunkSize),
			errors.New("Validation timed out"),
			50 * time.Millisecond,
		},
	}
	for _, test := range installTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{
				client: &mockClient{installClient: &mockInstallClient{
					reqMap:  test.reqMap,
					recv:    make(chan int, 1),
					recvErr: make(chan *pb.InstallResponse_InstallError, 1),
				}},
			}
			fileReader = test.reader
			if err := client.Install(context.Background(), "", "version", test.timeout); fmt.Sprintf("%v", err) != fmt.Sprintf("%v", test.err) {
				t.Errorf("Wanted error: **%v** but got error: **%v**", test.err, err)
			}
		})
	}
}

func TestActivate(t *testing.T) {
	activateTests := []struct {
		name    string
		client  *mockClient
		wantErr bool
	}{
		{
			"Success",
			&mockClient{activate: activateSuccessRPC},
			false,
		},
		{
			"Unspecified",
			&mockClient{activate: activateErrorRPC(pb.ActivateError_UNSPECIFIED, "detail")},
			true,
		},
		{
			"Non Existent Version",
			&mockClient{activate: activateErrorRPC(pb.ActivateError_NON_EXISTENT_VERSION, "")},
			true,
		},
	}
	for _, test := range activateTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{client: test.client}
			got := client.Activate(context.Background(), "version")
			if test.wantErr {
				if got == nil {
					t.Error("want error, got nil")
				}
			} else if got != nil {
				t.Errorf("want <nil>, got: %v", got)
			}
		})
	}
}

func TestVerify(t *testing.T) {
	verifyTests := []struct {
		name,
		runningVersion,
		failMessage string
	}{
		{"Is Running", "version", ""},
		{"Previous Activation Fail", "version", "Activation fail"},
	}
	for _, test := range verifyTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{client: &mockClient{
				verify: func(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error) {
					return &pb.VerifyResponse{Version: test.runningVersion, ActivationFailMessage: test.failMessage}, nil
				},
			}}
			version, activationErr, err := client.Verify(context.Background())
			if err != nil {
				t.Errorf("Verify RPC error: %w", err)
			}
			if activationErr != test.failMessage || version != test.runningVersion {
				t.Errorf(
					"Expected VerifyResponse(%s, %s) but got VerifyResponse(%s, %s)",
					test.runningVersion,
					test.failMessage,
					version,
					activationErr,
				)
			}
		})
	}
}
<commit_msg>Added Non nil return for nil response (#148)<commit_after>/* Copyright 2020 Google Inc.

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

    https://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 os

import (
	"bytes"
	"context"
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"reflect"
	"testing"
	"time"

	"github.com/google/gnxi/gnoi/os/pb"
	"google.golang.org/grpc"
)

type activateRPC func(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error)
type verifyRPC func(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error)

type mockClient struct {
	pb.OSClient
	activate      activateRPC
	verify        verifyRPC
	installClient pb.OS_InstallClient
}

type installRequestMap struct {
	req  *pb.InstallRequest
	resp *pb.InstallResponse
}

type mockInstallClient struct {
	pb.OS_InstallClient
	reqMap  []*installRequestMap
	i       int
	recv    chan int
	recvErr chan *pb.InstallResponse_InstallError
}

func (c *mockInstallClient) Send(req *pb.InstallRequest) error {
	if c.i < len(c.reqMap) {
		if reflect.TypeOf(req.Request) == reflect.TypeOf(c.reqMap[c.i].req.Request) {
			c.recv <- c.i
		} else {
			c.recvErr <- &pb.InstallResponse_InstallError{
				InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Invalid command"},
			}
		}
		c.i++
	}
	return nil
}

func (c *mockInstallClient) Recv() (*pb.InstallResponse, error) {
	select {
	case i := <-c.recv:
		res := c.reqMap[i].resp
		if res == nil {
			<-time.After(200 * time.Millisecond)
			return &pb.InstallResponse{
				Response: &pb.InstallResponse_InstallError{
					InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Invalid command"},
				},
			}, nil
		}
		return res, nil
	case err := <-c.recvErr:
		return &pb.InstallResponse{Response: err}, nil
	}
}

func (c *mockInstallClient) CloseSend() error {
	return nil
}

func (c *mockClient) Activate(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
	return c.activate(ctx, in, opts...)
}

func (c *mockClient) Verify(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error) {
	return c.verify(ctx, in, opts...)
}

func (c *mockClient) Install(ctx context.Context, opts ...grpc.CallOption) (pb.OS_InstallClient, error) {
	return c.installClient, nil
}

func activateErrorRPC(errType pb.ActivateError_Type, detail string) activateRPC {
	return func(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
		return &pb.ActivateResponse{
			Response: &pb.ActivateResponse_ActivateError{
				ActivateError: &pb.ActivateError{Type: errType, Detail: detail},
			}}, nil
	}
}

func activateSuccessRPC(ctx context.Context, in *pb.ActivateRequest, opts ...grpc.CallOption) (*pb.ActivateResponse, error) {
	return &pb.ActivateResponse{
		Response: &pb.ActivateResponse_ActivateOk{}}, nil
}

func readBytes(num int) func(string) (io.ReaderAt, uint64, func() error, error) {
	b := make([]byte, num)
	rand.Read(b)
	return func(_ string) (io.ReaderAt, uint64, func() error, error) {
		return bytes.NewReader(b), uint64(num), func() error { return nil }, nil
	}
}

func TestInstall(t *testing.T) {
	installTests := []struct {
		name    string
		reqMap  []*installRequestMap
		reader  func(string) (io.ReaderAt, uint64, func() error, error)
		err     error
		timeout time.Duration
	}{
		{
			"Already validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes(0),
			nil,
			100 * time.Millisecond,
		},
		{
			"File size of one chunk then validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: chunkSize}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferEnd{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes(chunkSize),
			nil,
			100 * time.Millisecond,
		},
		{
			"File size of two chunks + 1 then validated",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: chunkSize}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: (chunkSize * 2)}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: (chunkSize * 2) + 1}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferEnd{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_Validated{Validated: &pb.Validated{}}},
				},
			},
			readBytes((chunkSize * 2) + 1),
			nil,
			100 * time.Millisecond,
		},
		{
			"File size of one chunk but INCOMPATIBLE InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_INCOMPATIBLE}}},
				},
			},
			readBytes(chunkSize),
			errors.New("InstallError occured: INCOMPATIBLE"),
			100 * time.Millisecond,
		},
		{
			"TransferRequest but INCOMPATIBLE InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_INCOMPATIBLE}}},
				},
			},
			readBytes(0),
			errors.New("InstallError occured: INCOMPATIBLE"),
			100 * time.Millisecond,
		},
		{
			"TransferRequest but Unspecified InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Unspecified"}}},
				},
			},
			readBytes(0),
			errors.New("Unspecified InstallError error: Unspecified"),
			100 * time.Millisecond,
		},
		{
			"File size of two chunks but Unspecified InstallError",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_InstallError{InstallError: &pb.InstallError{Type: pb.InstallError_UNSPECIFIED, Detail: "Unspecified"}}},
				},
			},
			readBytes(chunkSize * 2),
			errors.New("Unspecified InstallError error: Unspecified"),
			100 * time.Millisecond,
		},
		{
			"File size of one chunk then timout",
			[]*installRequestMap{
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferRequest{TransferRequest: &pb.TransferRequest{Version: "version"}}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferReady{}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferContent{}},
					&pb.InstallResponse{Response: &pb.InstallResponse_TransferProgress{TransferProgress: &pb.TransferProgress{BytesReceived: chunkSize}}},
				},
				{
					&pb.InstallRequest{Request: &pb.InstallRequest_TransferEnd{}},
					nil,
				},
			},
			readBytes(chunkSize),
			errors.New("Validation timed out"),
			50 * time.Millisecond,
		},
	}
	for _, test := range installTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{
				client: &mockClient{installClient: &mockInstallClient{
					reqMap:  test.reqMap,
					recv:    make(chan int, 1),
					recvErr: make(chan *pb.InstallResponse_InstallError, 1),
				}},
			}
			fileReader = test.reader
			if err := client.Install(context.Background(), "", "version", test.timeout); fmt.Sprintf("%v", err) != fmt.Sprintf("%v", test.err) {
				t.Errorf("Wanted error: **%v** but got error: **%v**", test.err, err)
			}
		})
	}
}

func TestActivate(t *testing.T) {
	activateTests := []struct {
		name    string
		client  *mockClient
		wantErr bool
	}{
		{
			"Success",
			&mockClient{activate: activateSuccessRPC},
			false,
		},
		{
			"Unspecified",
			&mockClient{activate: activateErrorRPC(pb.ActivateError_UNSPECIFIED, "detail")},
			true,
		},
		{
			"Non Existent Version",
			&mockClient{activate: activateErrorRPC(pb.ActivateError_NON_EXISTENT_VERSION, "")},
			true,
		},
	}
	for _, test := range activateTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{client: test.client}
			got := client.Activate(context.Background(), "version")
			if test.wantErr {
				if got == nil {
					t.Error("want error, got nil")
				}
			} else if got != nil {
				t.Errorf("want <nil>, got: %v", got)
			}
		})
	}
}

func TestVerify(t *testing.T) {
	verifyTests := []struct {
		name,
		runningVersion,
		failMessage string
	}{
		{"Is Running", "version", ""},
		{"Previous Activation Fail", "version", "Activation fail"},
	}
	for _, test := range verifyTests {
		t.Run(test.name, func(t *testing.T) {
			client := Client{client: &mockClient{
				verify: func(ctx context.Context, in *pb.VerifyRequest, opts ...grpc.CallOption) (*pb.VerifyResponse, error) {
					return &pb.VerifyResponse{Version: test.runningVersion, ActivationFailMessage: test.failMessage}, nil
				},
			}}
			version, activationErr, err := client.Verify(context.Background())
			if err != nil {
				t.Errorf("Verify RPC error: %w", err)
			}
			if activationErr != test.failMessage || version != test.runningVersion {
				t.Errorf(
					"Expected VerifyResponse(%s, %s) but got VerifyResponse(%s, %s)",
					test.runningVersion,
					test.failMessage,
					version,
					activationErr,
				)
			}
		})
	}
}
<|endoftext|>


<commit_before>package x86_16

import (
	cs "github.com/bnagy/gapstone"
	ks "github.com/keystone-engine/keystone/bindings/go/keystone"
	uc "github.com/unicorn-engine/unicorn/bindings/go/unicorn"

	"github.com/lunixbochs/usercorn/go/cpu"
	"github.com/lunixbochs/usercorn/go/cpu/unicorn"
	"github.com/lunixbochs/usercorn/go/models"
)

var Arch = &models.Arch{
	Bits:   16,
	Radare: "x86_16", // TODO: Test

	Cpu: &unicorn.Builder{Arch: uc.ARCH_X86, Mode: uc.MODE_16},
	Dis: &cpu.Capstone{Arch: cs.CS_ARCH_X86, Mode: cs.CS_MODE_16},
	Asm: &cpu.Keystone{Arch: ks.ARCH_X86, Mode: ks.MODE_16},

	PC: uc.X86_REG_IP,
	SP: uc.X86_REG_SP,
	Regs: map[string]int{
		"ip": uc.X86_REG_IP,
		"sp": uc.X86_REG_SP,
		"bp": uc.X86_REG_BP,
		"ax": uc.X86_REG_AX,
		"bx": uc.X86_REG_BX,
		"cx": uc.X86_REG_CX,
		"dx": uc.X86_REG_DX,
		"si": uc.X86_REG_SI,
		"di": uc.X86_REG_DI,

		"flags": uc.X86_REG_EFLAGS,

		"cs": uc.X86_REG_CS,
		"ds": uc.X86_REG_DS,
		"es": uc.X86_REG_ES,
		"ss": uc.X86_REG_SS,
	},
	DefaultRegs: []string{
		"ax", "bx", "cx", "dx", "si", "di", "bp",
	},
	GdbXml: "", // TODO: Find this GDB xml for x86_16
}
<commit_msg>tweak x86_16 arch def<commit_after>package x86_16

import (
	cs "github.com/bnagy/gapstone"
	ks "github.com/keystone-engine/keystone/bindings/go/keystone"
	uc "github.com/unicorn-engine/unicorn/bindings/go/unicorn"

	"github.com/lunixbochs/usercorn/go/cpu"
	"github.com/lunixbochs/usercorn/go/cpu/unicorn"
	"github.com/lunixbochs/usercorn/go/models"
)

var Arch = &models.Arch{
	Name:   "x86_16",
	Bits:   16,
	Radare: "x86",

	Cpu: &unicorn.Builder{Arch: uc.ARCH_X86, Mode: uc.MODE_16},
	Dis: &cpu.Capstone{Arch: cs.CS_ARCH_X86, Mode: cs.CS_MODE_16},
	Asm: &cpu.Keystone{Arch: ks.ARCH_X86, Mode: ks.MODE_16},

	PC: uc.X86_REG_IP,
	SP: uc.X86_REG_SP,
	Regs: map[string]int{
		"ip": uc.X86_REG_IP,
		"sp": uc.X86_REG_SP,
		"bp": uc.X86_REG_BP,
		"ax": uc.X86_REG_AX,
		"bx": uc.X86_REG_BX,
		"cx": uc.X86_REG_CX,
		"dx": uc.X86_REG_DX,
		"si": uc.X86_REG_SI,
		"di": uc.X86_REG_DI,

		"flags": uc.X86_REG_EFLAGS,

		"cs": uc.X86_REG_CS,
		"ds": uc.X86_REG_DS,
		"es": uc.X86_REG_ES,
		"ss": uc.X86_REG_SS,
	},
	DefaultRegs: []string{
		"ax", "bx", "cx", "dx", "si", "di", "bp",
	},
	GdbXml: "", // TODO: Find this GDB xml for x86_16
}
<|endoftext|>


<commit_before>// Copyright 2020 Anapaya Systems
//
// 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 config

const idSample = "cs-1"

const psSample = `
# The time after which segments for a destination are refetched. (default 5m)
query_interval = "5m"
# The path to the hidden paths configuration file. If the path is empty, hidden
# paths functionality is not enabled. If the path starts with http:// or
# https:// the configuration is fetched from the given URL. (default: "")
hidden_paths_cfg = ""
`

const caSample = `
# The maximum validity time of a renewed AS certificate the control server
# creates in a CA AS. The remaining validity of the locally available CA
# certificate must be larger than the here configured value at every given point
# in time. (i.e., ca.not_after - current_time >= max_as_validity) If that is not
# the case, certificate renewal is not possible until a new CA certificate is
# loaded that satisfies the condition. (default 3d)
max_as_validity = "3d"

# The mode the CA handler of this control service operates in.
#
# - in-process: In this mode, the certificates are renewed in the control
#               service process. This means it needs access to the CA private
#               key and a currently active CA certificate.
#
# - delegated: In this mode, the certificate renewal is delegated to the CA
#              service via an API call. This means the service needs to be
#              configured with the CA service address and the secrets to
#              authenticate itself. Note that legacy requests will always
#              be handled in-process, even if delegated mode is selected.
#
# (default in-process)
mode = "in-process"

# Disable handling of the legacy certificate renewal requests.
# This option is temporary and will be removed with the support for
# legacy renewal requests. (default false)
disable_legacy_request = false
`

const serviceSample = `
# The path to the PEM-encoded shared secret that is used to create JWT tokens.
shared_secret = ""
# The address of the CA Service that handles the delegated certificate renewal requests.
addr = ""
# The validity period of self-generated JWT authorization tokens. The format
# is a Go duration. If not set, the application default in this sample is used instead.
lifetime = "10m"
# The client identification string that should be used in self-generated JWT
# authorization tokens. If not set, the SCION ID is used instead.
client_id = ""
`
<commit_msg>cs: fix sample text for delegating CA<commit_after>// Copyright 2020 Anapaya Systems
//
// 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 config

const idSample = "cs-1"

const psSample = `
# The time after which segments for a destination are refetched. (default 5m)
query_interval = "5m"
# The path to the hidden paths configuration file. If the path is empty, hidden
# paths functionality is not enabled. If the path starts with http:// or
# https:// the configuration is fetched from the given URL. (default: "")
hidden_paths_cfg = ""
`

const caSample = `
# The maximum validity time of a renewed AS certificate the control server
# creates in a CA AS. The remaining validity of the locally available CA
# certificate must be larger than the here configured value at every given point
# in time. (i.e., ca.not_after - current_time >= max_as_validity) If that is not
# the case, certificate renewal is not possible until a new CA certificate is
# loaded that satisfies the condition. (default 3d)
max_as_validity = "3d"

# The mode the CA handler of this control service operates in.
#
# - in-process: In this mode, the certificates are renewed in the control
#               service process. This means it needs access to the CA private
#               key and a currently active CA certificate.
#
# - delegating: In this mode, the certificate renewal is delegated to the CA
#               service via an API call. This means the service needs to be
#               configured with the CA service address and the secrets to
#               authenticate itself. Note that legacy requests will always
#               be handled in-process, even if delegating mode is selected.
#
# (default in-process)
mode = "in-process"

# Disable handling of the legacy certificate renewal requests.
# This option is temporary and will be removed with the support for
# legacy renewal requests. (default false)
disable_legacy_request = false
`

const serviceSample = `
# The path to the PEM-encoded shared secret that is used to create JWT tokens.
shared_secret = ""
# The address of the CA Service that handles the delegated certificate renewal requests.
addr = ""
# The validity period of self-generated JWT authorization tokens. The format
# is a Go duration. If not set, the application default in this sample is used instead.
lifetime = "10m"
# The client identification string that should be used in self-generated JWT
# authorization tokens. If not set, the SCION ID is used instead.
client_id = ""
`
<|endoftext|>


<commit_before>package paddlecloud

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"net/url"
	"os"
	"strconv"

	"github.com/PaddlePaddle/cloud/go/utils"
	"github.com/google/subcommands"
)

// LogsCommand print aggregated job logs
type LogsCommand struct {
	n int
	w string
}

// Name is subcommands name
func (*LogsCommand) Name() string { return "logs" }

// Synopsis is subcommands synopsis
func (*LogsCommand) Synopsis() string { return "Print logs of the job." }

// Usage is subcommands usage
func (*LogsCommand) Usage() string {
	return `logs <job name>:
	Print logs of the job.
	Options:
`
}

// SetFlags registers subcommands flags
func (p *LogsCommand) SetFlags(f *flag.FlagSet) {
	f.IntVar(&p.n, "n", 10, "Number of lines to print from tail.")
	f.StringVar(&p.w, "w", "", "Print logs for a single worker.")
}

// Execute logs command
func (p *LogsCommand) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
	if f.NArg() != 1 {
		f.Usage()
		return subcommands.ExitFailure
	}

	var queryMap url.Values
	queryMap.Add("n", strconv.FormatInt(int64(p.n), 10))
	queryMap.Add("w", p.w)
	queryMap.Add("jobname", f.Arg(0))

	respBody, err := utils.GetCall(utils.Config.ActiveConfig.Endpoint+"/api/v1/logs", queryMap)
	if err != nil {
		fmt.Fprintf(os.Stderr, "call paddle cloud error %v", err)
	}
	var respObj interface{}
	errJSON := json.Unmarshal(respBody, &respObj)
	if errJSON != nil {
		fmt.Fprintf(os.Stderr, "bad server return: %s", respBody)
	}
	fmt.Printf("%s\n", respObj.(map[string]interface{})["msg"].(string))
	return subcommands.ExitSuccess
}
<commit_msg>fix log command error<commit_after>package paddlecloud

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"net/url"
	"os"
	"strconv"

	"github.com/PaddlePaddle/cloud/go/utils"
	"github.com/google/subcommands"
)

// LogsCommand print aggregated job logs
type LogsCommand struct {
	n int
	w string
}

// Name is subcommands name
func (*LogsCommand) Name() string { return "logs" }

// Synopsis is subcommands synopsis
func (*LogsCommand) Synopsis() string { return "Print logs of the job." }

// Usage is subcommands usage
func (*LogsCommand) Usage() string {
	return `logs <job name>:
	Print logs of the job.
	Options:
`
}

// SetFlags registers subcommands flags
func (p *LogsCommand) SetFlags(f *flag.FlagSet) {
	f.IntVar(&p.n, "n", 10, "Number of lines to print from tail.")
	f.StringVar(&p.w, "w", "", "Print logs for a single worker.")
}

// Execute logs command
func (p *LogsCommand) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
	if f.NArg() != 1 {
		f.Usage()
		return subcommands.ExitFailure
	}

	var queryMap url.Values
	queryMap.Add("n", strconv.FormatInt(int64(p.n), 10))
	queryMap.Add("w", p.w)
	queryMap.Add("jobname", f.Arg(0))

	respBody, err := utils.GetCall(utils.Config.ActiveConfig.Endpoint+"/api/v1/logs", queryMap)
	if err != nil {
		fmt.Fprintf(os.Stderr, "call paddle cloud error %v, %v", err, respBody)
		return subcommands.ExitFailure
	}
	var respObj interface{}
	errJSON := json.Unmarshal(respBody, &respObj)
	if errJSON != nil {
		fmt.Fprintf(os.Stderr, "bad server return: %s", respBody)
	}
	fmt.Printf("%s\n", respObj.(map[string]interface{})["msg"].(string))
	return subcommands.ExitSuccess
}
<|endoftext|>


<commit_before>package health

import (
	"fmt"
	"reflect"
	"sync"
	"time"

	log "github.com/golang/glog"
	"github.com/youtube/vitess/go/history"
	"github.com/youtube/vitess/go/vt/concurrency"
	"github.com/youtube/vitess/go/vt/topo"
)

var (
	defaultAggregator *Aggregator
)

const (
	// ReplicationLag should be the key for any reporters
	// reporting MySQL repliaction lag.
	ReplicationLag = "replication_lag"

	// ReplicationLagHigh should be the value for any reporters
	// indicating that the replication lag is too high.
	ReplicationLagHigh = "high"

	historyLength = 16
)

const (
	Healthy   = "healthy"
	Unhappy   = "unhappy"
	Unhealthy = "unhealthy"
	Unknown   = "unknown"
)

func init() {
	defaultAggregator = NewAggregator()
}

// Reporter reports the health status of a tablet.
type Reporter interface {
	// Report returns a map of health states for the tablet
	// assuming that its tablet type is typ. If Report returns an
	// error it implies that the tablet is in a bad shape and not
	// able to handle queries.
	Report(typ topo.TabletType) (status map[string]string, err error)
}

// FunctionReporter is a function that may act as a Reporter.
type FunctionReporter func(typ topo.TabletType) (map[string]string, error)

func (fc FunctionReporter) Report(typ topo.TabletType) (status map[string]string, err error) {
	return fc(typ)
}

// Aggregator aggregates the results of many Reporters.
type Aggregator struct {
	History *history.History

	// mu protects all fields below its declaration.
	mu        sync.Mutex
	reporters map[string]Reporter
}

func NewAggregator() *Aggregator {
	return &Aggregator{
		History:   history.New(historyLength),
		reporters: make(map[string]Reporter),
	}
}

// Record records one run of an aggregator.
type Record struct {
	Error  error
	Result map[string]string
	Time   time.Time
}

func (r Record) Class() string {
	switch {
	case r.Error != nil:
		return Unhealthy
	case len(r.Result) > 0:
		return Unhappy
	default:
		return Healthy
	}
}

func (r Record) IsDuplicate(other interface{}) bool {
	rother, ok := other.(Record)
	if !ok {
		return false
	}
	return reflect.DeepEqual(r.Error, rother.Error) && reflect.DeepEqual(r.Result, rother.Result)
}

// Run runs aggregates health statuses from all the reporters. If any
// errors occur during the reporting, they will be logged, but only
// the first error will be returned.
func (ag *Aggregator) Run(typ topo.TabletType) (map[string]string, error) {
	var (
		wg  sync.WaitGroup
		rec concurrency.AllErrorRecorder
	)

	record := Record{
		Time:   time.Now(),
		Result: make(map[string]string),
	}

	defer ag.History.Add(record)

	results := make(chan map[string]string, len(ag.reporters))
	ag.mu.Lock()
	for name, rep := range ag.reporters {
		name, rep := name, rep
		wg.Add(1)

		go func() {
			defer wg.Done()
			status, err := rep.Report(typ)
			if err != nil {
				log.Errorf("reporter %v: %v", name, err)
				rec.RecordError(err)
				return
			}
			results <- status
		}()
	}
	ag.mu.Unlock()
	wg.Wait()
	close(results)
	if record.Error = rec.Error(); record.Error != nil {
		return nil, record.Error
	}
	for part := range results {
		for k, v := range part {
			if _, ok := record.Result[k]; ok {
				return nil, fmt.Errorf("duplicate key: %v", k)
			}
			record.Result[k] = v
		}
	}
	return record.Result, nil
}

// Register registers rep with ag. Only keys specified in keys will be
// aggregated from this particular Reporter.
func (ag *Aggregator) Register(name string, rep Reporter) {
	ag.mu.Lock()
	defer ag.mu.Unlock()
	if _, ok := ag.reporters[name]; ok {
		panic("reporter named " + name + " is already registered")
	}
	ag.reporters[name] = rep

}

// Run collects all the health statuses from the default health
// aggregator.
func Run(typ topo.TabletType) (map[string]string, error) {
	return defaultAggregator.Run(typ)
}

// Register registers rep under name with the default health
// aggregator. Only keys specified in keys will be aggregated from
// this particular Reporter.
func Register(name string, rep Reporter) {
	defaultAggregator.Register(name, rep)
}

// History returns the health records from the default health
// aggregator.
func History() []interface{} {
	return defaultAggregator.History.Records()
}
<commit_msg>Fix silly bug in health module.<commit_after>package health

import (
	"fmt"
	"reflect"
	"sync"
	"time"

	log "github.com/golang/glog"
	"github.com/youtube/vitess/go/history"
	"github.com/youtube/vitess/go/vt/concurrency"
	"github.com/youtube/vitess/go/vt/topo"
)

var (
	defaultAggregator *Aggregator
)

const (
	// ReplicationLag should be the key for any reporters
	// reporting MySQL repliaction lag.
	ReplicationLag = "replication_lag"

	// ReplicationLagHigh should be the value for any reporters
	// indicating that the replication lag is too high.
	ReplicationLagHigh = "high"

	historyLength = 16
)

const (
	Healthy   = "healthy"
	Unhappy   = "unhappy"
	Unhealthy = "unhealthy"
	Unknown   = "unknown"
)

func init() {
	defaultAggregator = NewAggregator()
}

// Reporter reports the health status of a tablet.
type Reporter interface {
	// Report returns a map of health states for the tablet
	// assuming that its tablet type is typ. If Report returns an
	// error it implies that the tablet is in a bad shape and not
	// able to handle queries.
	Report(typ topo.TabletType) (status map[string]string, err error)
}

// FunctionReporter is a function that may act as a Reporter.
type FunctionReporter func(typ topo.TabletType) (map[string]string, error)

func (fc FunctionReporter) Report(typ topo.TabletType) (status map[string]string, err error) {
	return fc(typ)
}

// Aggregator aggregates the results of many Reporters.
type Aggregator struct {
	History *history.History

	// mu protects all fields below its declaration.
	mu        sync.Mutex
	reporters map[string]Reporter
}

func NewAggregator() *Aggregator {
	return &Aggregator{
		History:   history.New(historyLength),
		reporters: make(map[string]Reporter),
	}
}

// Record records one run of an aggregator.
type Record struct {
	Error  error
	Result map[string]string
	Time   time.Time
}

func (r Record) Class() string {
	switch {
	case r.Error != nil:
		return Unhealthy
	case len(r.Result) > 0:
		return Unhappy
	default:
		return Healthy
	}
}

func (r Record) IsDuplicate(other interface{}) bool {
	rother, ok := other.(Record)
	if !ok {
		return false
	}
	return reflect.DeepEqual(r.Error, rother.Error) && reflect.DeepEqual(r.Result, rother.Result)
}

// Run runs aggregates health statuses from all the reporters. If any
// errors occur during the reporting, they will be logged, but only
// the first error will be returned.
func (ag *Aggregator) Run(typ topo.TabletType) (map[string]string, error) {
	var (
		wg  sync.WaitGroup
		rec concurrency.AllErrorRecorder
	)

	record := Record{
		Time:   time.Now(),
		Result: make(map[string]string),
	}
	defer func() { ag.History.Add(record) }()

	results := make(chan map[string]string, len(ag.reporters))
	ag.mu.Lock()
	for name, rep := range ag.reporters {
		name, rep := name, rep
		wg.Add(1)

		go func() {
			defer wg.Done()
			status, err := rep.Report(typ)
			if err != nil {
				log.Errorf("reporter %v: %v", name, err)
				rec.RecordError(err)
				return
			}
			results <- status
		}()
	}
	ag.mu.Unlock()
	wg.Wait()
	close(results)
	if record.Error = rec.Error(); record.Error != nil {
		return nil, record.Error
	}
	for part := range results {
		for k, v := range part {
			if _, ok := record.Result[k]; ok {
				return nil, fmt.Errorf("duplicate key: %v", k)
			}
			record.Result[k] = v
		}
	}
	return record.Result, nil
}

// Register registers rep with ag. Only keys specified in keys will be
// aggregated from this particular Reporter.
func (ag *Aggregator) Register(name string, rep Reporter) {
	ag.mu.Lock()
	defer ag.mu.Unlock()
	if _, ok := ag.reporters[name]; ok {
		panic("reporter named " + name + " is already registered")
	}
	ag.reporters[name] = rep

}

// Run collects all the health statuses from the default health
// aggregator.
func Run(typ topo.TabletType) (map[string]string, error) {
	return defaultAggregator.Run(typ)
}

// Register registers rep under name with the default health
// aggregator. Only keys specified in keys will be aggregated from
// this particular Reporter.
func Register(name string, rep Reporter) {
	defaultAggregator.Register(name, rep)
}

// History returns the health records from the default health
// aggregator.
func History() []interface{} {
	return defaultAggregator.History.Records()
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors 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.
*/

// The go2docker command compiles a go main package and forge a minimal
// docker image from the resulting static binary.
//
// usage: go2docker [-image namespace/basename] go/pkg/path | docker load
package main

import (
	"archive/tar"
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"time"
)

type config struct {
	Cmd []string `json:"Cmd"`
}

type image struct {
	ID            string    `json:"id"`
	Created       time.Time `json:"created"`
	DockerVersion string    `json:"docker_version"`
	Config        config    `json:"config"`
	Architecture  string    `json:"architecture"`
	OS            string    `json:"os"`
}

var imageName = flag.String("image", "", "namespace/name for the repository, default to go2docker/$(basename)")

const (
	dockerVersion = "1.4.0"
	arch          = "amd64"
	goos          = "linux"
	version       = "1.0"
	namespace     = "go2docker"
)

func main() {
	flag.Parse()
	args := []string{"."}
	if flag.NArg() > 0 {
		args = flag.Args()
	}

	// check for go
	goBin, err := exec.LookPath("go")
	if err != nil {
		log.Fatalf("`go` executable not found: %v, see %q ", err, "golang.org/doc/install")
	}
	toolPath := filepath.Dir(goBin)
	// check for linux_amd64 toolchain
	crossPath := filepath.Join(toolPath, "linux_amd64")
	if _, crossErr := os.Stat(crossPath); os.IsNotExist(crossErr) {
		// check for make.bash
		makeBash, err := filepath.Abs(filepath.Join(toolPath, "..", "src", "make.bash"))
		if err != nil {
			log.Fatalf("failed to resolve make.bash path: %v", err)
		}
		if _, err := os.Stat(makeBash); os.IsNotExist(err) {
			log.Fatalf("`make.bash` not found %q: %v", makeBash, err)
		}
		makeBashCmd := fmt.Sprintf("(cd %s; GOOS=linux GOARCH=amd64 ./make.bash --no-clean)", filepath.Dir(makeBash))
		log.Fatalf("`go %s` toolchain not found: %v, run: %q", "linux_amd64", crossErr, makeBashCmd)
	}

	fpath, err := filepath.Abs(args[0])
	ext := filepath.Ext(fpath)
	basename := filepath.Base(fpath[:len(fpath)-len(ext)])

	if *imageName == "" {
		if err != nil {
			log.Fatalf("failed to get absolute path: %v", err)
		}
		*imageName = path.Join(namespace, basename)
	}
	tmpDir, err := ioutil.TempDir("", "")
	if err != nil {
		log.Fatalf("failed to create temp directory: %v", err)
	}
	aout := filepath.Join(tmpDir, basename)
	command := append([]string{"go", "build", "-o", aout, "-a", "-tags", "netgo", "-installsuffix", "netgo"}, args...)
	cmd := exec.Command(command[0], command[1:]...)
	gopath := os.Getenv("GOPATH")
	cmd.Env = []string{
		"GOOS=linux",
		"GOARCH=amd64",
		"GOPATH=" + gopath,
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		log.Fatalf("failed to get process stderr: %v", err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatalf("failed to start command %q: %v", strings.Join(command, " "), err)
	}
	io.Copy(os.Stderr, stderr)
	if err := cmd.Wait(); err != nil {
		log.Fatalf("command %q failed: %v", strings.Join(command, " "), err)
	}

	imageIDBytes := make([]byte, 32)
	if _, err := rand.Read(imageIDBytes); err != nil {
		log.Fatalf("failed to generate ID: %v", err)
	}
	imageID := hex.EncodeToString(imageIDBytes)
	repo := map[string]map[string]string{
		*imageName: {
			"latest": imageID,
		},
	}
	repoJSON, err := json.Marshal(repo)
	if err != nil {
		log.Fatalf("failed to serialize repo %#v: %v", repo, err)
	}
	tw := tar.NewWriter(os.Stdout)
	if err := tw.WriteHeader(&tar.Header{
		Name: "repositories",
		Size: int64(len(repoJSON)),
	}); err != nil {
		log.Fatalf("failed to write /repository header: %v", err)
	}
	if _, err := tw.Write(repoJSON); err != nil {
		log.Fatalf(" failed to write /repository body: %v", err)
	}
	if err := tw.WriteHeader(&tar.Header{
		Name: imageID + "/VERSION",
		Size: int64(len(version)),
	}); err != nil {
		log.Fatalf("failed to write /%s/VERSION header: %v", imageID, err)
	}
	if _, err := tw.Write([]byte(version)); err != nil {
		log.Fatalf(" failed to write /%s/VERSION body: %v", imageID, err)
	}
	imageJSON, err := json.Marshal(image{
		ID:            imageID,
		Created:       time.Now().UTC(),
		DockerVersion: version,
		Config: config{
			Cmd: []string{"/" + basename},
		},
		Architecture: arch,
		OS:           goos,
	})
	if err := tw.WriteHeader(&tar.Header{
		Name: imageID + "/json",
		Size: int64(len(imageJSON)),
	}); err != nil {
		log.Fatalf("failed to write /%s/json header: %v", imageID, err)
	}
	if _, err := tw.Write(imageJSON); err != nil {
		log.Fatalf("failed to write /%s/json body: %v", imageID, err)
	}
	var buf bytes.Buffer
	ftw := tar.NewWriter(&buf)
	file, err := os.Open(aout)
	if err != nil {
		log.Fatalf("failed to open %q: %v", aout, err)
	}
	finfo, err := file.Stat()
	if err != nil {
		log.Fatalf("failed to get file info %q: %v", aout, err)
	}
	fheader, err := tar.FileInfoHeader(finfo, "")
	if err != nil {
		log.Fatalf("failed to get file info header %q: %v", aout, err)
	}
	fheader.Name = basename
	if err := ftw.WriteHeader(fheader); err != nil {
		log.Fatalf("failed to write /%s header: %v", aout, err)
	}
	if _, err := io.Copy(ftw, file); err != nil {
		log.Fatalf("failed to write /%s body: %v", aout, err)
	}
	certBytes := []byte(caCerts)
	if err := ftw.WriteHeader(&tar.Header{
		Name: "/etc/ssl/certs/ca-certificates.crt",
		Size: int64(len(certBytes)),
	}); err != nil {
		log.Fatalf("failed to write ca-certificates.crt header: %v", err)
	}
	if _, err := ftw.Write(certBytes); err != nil {
		log.Fatalf("failed to write ca-certificates.crt body: %v", err)
	}
	if err := ftw.Close(); err != nil {
		log.Fatalf("failed to close layer.tar: %v", err)
	}
	layerBytes := buf.Bytes()
	if err := tw.WriteHeader(&tar.Header{
		Name: imageID + "/layer.tar",
		Size: int64(len(layerBytes)),
	}); err != nil {
		log.Fatalf("failed to write /%s/layer.tar header: %v", imageID, err)
	}
	if _, err := tw.Write(layerBytes); err != nil {
		log.Fatalf("failed to write /%s/layer.tar body: %v", imageID, err)
	}
	if err := tw.Close(); err != nil {
		log.Fatalf("failed to close image.tar: %v", err)
	}
}
<commit_msg>go2docker: remove path assumptions for portability<commit_after>/*
Copyright 2014 The Kubernetes Authors 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.
*/

// The go2docker command compiles a go main package and forge a minimal
// docker image from the resulting static binary.
//
// usage: go2docker [-image namespace/basename] go/pkg/path | docker load
package main

import (
	"archive/tar"
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"time"
)

type config struct {
	Cmd []string `json:"Cmd"`
}

type image struct {
	ID            string    `json:"id"`
	Created       time.Time `json:"created"`
	DockerVersion string    `json:"docker_version"`
	Config        config    `json:"config"`
	Architecture  string    `json:"architecture"`
	OS            string    `json:"os"`
}

var imageName = flag.String("image", "", "namespace/name for the repository, default to go2docker/$(basename)")

const (
	dockerVersion = "1.4.0"
	arch          = "amd64"
	goos          = "linux"
	version       = "1.0"
	namespace     = "go2docker"
)

func main() {
	flag.Parse()
	args := []string{"."}
	if flag.NArg() > 0 {
		args = flag.Args()
	}

	// check for go in path and the linux_amd64 cross packages
	cmd := exec.Command("go", "env", "GOROOT")
	out, err := cmd.Output()
	if err != nil {
		log.Fatalf("`go env GOROOT` execution failed: %v, see %q ", err, "golang.org/doc/install")
	}
	goRoot := string(out[:len(out)-1])
	crossPath := filepath.Join(goRoot, "pkg", "linux_amd64")
	if _, crossErr := os.Stat(crossPath); os.IsNotExist(crossErr) {
		// check for make.bash
		makeBash, err := filepath.Abs(filepath.Join(goRoot, "src", "make.bash"))
		if err != nil {
			log.Fatalf("failed to resolve make.bash path: %v", err)
		}
		if _, err := os.Stat(makeBash); os.IsNotExist(err) {
			log.Fatalf("`make.bash` not found %q: %v", makeBash, err)
		}
		makeBashCmd := fmt.Sprintf("(cd %s; GOOS=linux GOARCH=amd64 ./make.bash --no-clean)", filepath.Dir(makeBash))
		log.Fatalf("`go %s` toolchain not found: %v, run: %q", "linux_amd64", crossErr, makeBashCmd)
	}

	fpath, err := filepath.Abs(args[0])
	ext := filepath.Ext(fpath)
	basename := filepath.Base(fpath[:len(fpath)-len(ext)])

	if *imageName == "" {
		if err != nil {
			log.Fatalf("failed to get absolute path: %v", err)
		}
		*imageName = path.Join(namespace, basename)
	}
	tmpDir, err := ioutil.TempDir("", "")
	if err != nil {
		log.Fatalf("failed to create temp directory: %v", err)
	}
	aout := filepath.Join(tmpDir, basename)
	command := append([]string{"go", "build", "-o", aout, "-a", "-tags", "netgo", "-installsuffix", "netgo"}, args...)
	cmd = exec.Command(command[0], command[1:]...)
	gopath := os.Getenv("GOPATH")
	cmd.Env = []string{
		"GOOS=linux",
		"GOARCH=amd64",
		"GOPATH=" + gopath,
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		log.Fatalf("failed to get process stderr: %v", err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatalf("failed to start command %q: %v", strings.Join(command, " "), err)
	}
	io.Copy(os.Stderr, stderr)
	if err := cmd.Wait(); err != nil {
		log.Fatalf("command %q failed: %v", strings.Join(command, " "), err)
	}

	imageIDBytes := make([]byte, 32)
	if _, err := rand.Read(imageIDBytes); err != nil {
		log.Fatalf("failed to generate ID: %v", err)
	}
	imageID := hex.EncodeToString(imageIDBytes)
	repo := map[string]map[string]string{
		*imageName: {
			"latest": imageID,
		},
	}
	repoJSON, err := json.Marshal(repo)
	if err != nil {
		log.Fatalf("failed to serialize repo %#v: %v", repo, err)
	}
	tw := tar.NewWriter(os.Stdout)
	if err := tw.WriteHeader(&tar.Header{
		Name: "repositories",
		Size: int64(len(repoJSON)),
	}); err != nil {
		log.Fatalf("failed to write /repository header: %v", err)
	}
	if _, err := tw.Write(repoJSON); err != nil {
		log.Fatalf(" failed to write /repository body: %v", err)
	}
	if err := tw.WriteHeader(&tar.Header{
		Name: imageID + "/VERSION",
		Size: int64(len(version)),
	}); err != nil {
		log.Fatalf("failed to write /%s/VERSION header: %v", imageID, err)
	}
	if _, err := tw.Write([]byte(version)); err != nil {
		log.Fatalf(" failed to write /%s/VERSION body: %v", imageID, err)
	}
	imageJSON, err := json.Marshal(image{
		ID:            imageID,
		Created:       time.Now().UTC(),
		DockerVersion: version,
		Config: config{
			Cmd: []string{"/" + basename},
		},
		Architecture: arch,
		OS:           goos,
	})
	if err := tw.WriteHeader(&tar.Header{
		Name: imageID + "/json",
		Size: int64(len(imageJSON)),
	}); err != nil {
		log.Fatalf("failed to write /%s/json header: %v", imageID, err)
	}
	if _, err := tw.Write(imageJSON); err != nil {
		log.Fatalf("failed to write /%s/json body: %v", imageID, err)
	}
	var buf bytes.Buffer
	ftw := tar.NewWriter(&buf)
	file, err := os.Open(aout)
	if err != nil {
		log.Fatalf("failed to open %q: %v", aout, err)
	}
	finfo, err := file.Stat()
	if err != nil {
		log.Fatalf("failed to get file info %q: %v", aout, err)
	}
	fheader, err := tar.FileInfoHeader(finfo, "")
	if err != nil {
		log.Fatalf("failed to get file info header %q: %v", aout, err)
	}
	fheader.Name = basename
	if err := ftw.WriteHeader(fheader); err != nil {
		log.Fatalf("failed to write /%s header: %v", aout, err)
	}
	if _, err := io.Copy(ftw, file); err != nil {
		log.Fatalf("failed to write /%s body: %v", aout, err)
	}
	certBytes := []byte(caCerts)
	if err := ftw.WriteHeader(&tar.Header{
		Name: "/etc/ssl/certs/ca-certificates.crt",
		Size: int64(len(certBytes)),
	}); err != nil {
		log.Fatalf("failed to write ca-certificates.crt header: %v", err)
	}
	if _, err := ftw.Write(certBytes); err != nil {
		log.Fatalf("failed to write ca-certificates.crt body: %v", err)
	}
	if err := ftw.Close(); err != nil {
		log.Fatalf("failed to close layer.tar: %v", err)
	}
	layerBytes := buf.Bytes()
	if err := tw.WriteHeader(&tar.Header{
		Name: imageID + "/layer.tar",
		Size: int64(len(layerBytes)),
	}); err != nil {
		log.Fatalf("failed to write /%s/layer.tar header: %v", imageID, err)
	}
	if _, err := tw.Write(layerBytes); err != nil {
		log.Fatalf("failed to write /%s/layer.tar body: %v", imageID, err)
	}
	if err := tw.Close(); err != nil {
		log.Fatalf("failed to close image.tar: %v", err)
	}
}
<|endoftext|>


<commit_before>package gofetcher

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/cocaine/cocaine-framework-go/cocaine"
	"github.com/ugorji/go/codec"
	"io"
	"io/ioutil"
	"net/http"
	"reflect"
	"strconv"
	"time"
)

const (
	DefaultTimeout         = 5000
	DefaultFollowRedirects = true
)

type Gofetcher struct {
	Logger *cocaine.Logger
}

type Cookies map[string]string

type Request struct {
	method          string
	url             string
	body            io.Reader
	timeout         int64
	cookies         Cookies
	headers         http.Header
	followRedirects bool
}

type responseAndError struct {
	res *http.Response
	err error
}

type Response struct {
	httpResponse *http.Response
	body         []byte
	header       http.Header
	runtime      time.Duration
}

func NewGofetcher() *Gofetcher {
	logger, err := cocaine.NewLogger()
	if err != nil {
		fmt.Printf("Could not initialize logger due to error: %v", err)
		return nil
	}
	gofetcher := Gofetcher{logger}
	return &gofetcher
}

func noRedirect(_ *http.Request, via []*http.Request) error {
	if len(via) > 0 {
		return errors.New("stopped after first redirect")
	}
	return nil
}

func (gofetcher *Gofetcher) performRequest(request *Request) (*Response, error) {
	var (
		err            error
		httpRequest    *http.Request
		httpResponse   *http.Response
		requestTimeout time.Duration = time.Duration(request.timeout) * time.Millisecond
	)
	gofetcher.Logger.Info(fmt.Sprintf("Requested url: %s, method: %s, timeout: %d, headers: %v",
		request.url, request.method, request.timeout, request.headers))
	httpClient := &http.Client{}
	if request.followRedirects == false {
		httpClient.CheckRedirect = noRedirect
	}
	httpRequest, err = http.NewRequest(request.method, request.url, request.body)
	if err != nil {
		return nil, err
	}
	for name, value := range request.cookies {
		httpRequest.AddCookie(&http.Cookie{Name: name, Value: value})
	}
	httpRequest.Header = request.headers
	resultChan := make(chan responseAndError)
	started := time.Now()
	go func() {
		res, err := httpClient.Do(httpRequest)
		if err != nil {
			resultChan <- responseAndError{nil, err}
		} else {
			resultChan <- responseAndError{res, err}
		}
	}()
	// http connection stay active after timeout exceeded, cause we can't close it using current client api.
	// Read more about timeouts: https://code.google.com/p/go/issues/detail?id=3362
	//
	select {
	case result := <-resultChan:
		httpResponse, err = result.res, result.err
	case <-time.After(requestTimeout):
		err = errors.New(fmt.Sprintf("Request timeout[%s] exceeded", requestTimeout.String()))
		go func() {
			// close httpResponse when it ready
			result := <-resultChan
			if result.res != nil {
				result.res.Body.Close()
			}
		}()
	}
	if err != nil {
		return nil, err
	}
	defer httpResponse.Body.Close()
	body, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		return nil, err
	}
	runtime := time.Since(started)
	response := &Response{httpResponse: httpResponse, body: body, header: httpResponse.Header, runtime: runtime}
	return response, nil

}

// Normal methods

func parseHeaders(rawHeaders map[string]interface{}) http.Header {
	headers := make(http.Header)
	for name, values := range rawHeaders {
		for _, value := range values.([]interface{}) {
			headers.Add(name, string(value.([]uint8))) // to transform in canonical form
		}
	}
	return headers
}

func parseCookies(rawCookie map[string]interface{}) Cookies {
	cookies := Cookies{}
	for key, value := range rawCookie {
		cookies[key] = string(value.([]uint8))
	}
	return cookies
}

func parseTimeout(rawTimeout interface{}) (timeout int64) {
	// is it possible to got timeout in int64 instead of uint64?
	switch rawTimeout.(type) {
	case uint64:
		timeout = int64(rawTimeout.(uint64))
	case int64:
		timeout = rawTimeout.(int64)
	}
	return timeout
}

func (gofetcher *Gofetcher) parseRequest(method string, requestBody []byte) (request *Request) {
	var (
		mh              codec.MsgpackHandle
		h                     = &mh
		timeout         int64 = DefaultTimeout
		cookies         Cookies
		headers              = make(http.Header)
		followRedirects bool = DefaultFollowRedirects
		body            *bytes.Buffer
	)
	mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
	var res []interface{}
	codec.NewDecoderBytes(requestBody, h).Decode(&res)
	url := string(res[0].([]uint8))
	switch {
	case method == "GET" || method == "HEAD" || method == "DELETE":
		if len(res) > 1 {
			timeout = parseTimeout(res[1])
		}
		if len(res) > 2 {
			cookies = parseCookies(res[2].(map[string]interface{}))
		}
		if len(res) > 3 {
			headers = parseHeaders(res[3].(map[string]interface{}))
		}
		if len(res) > 4 {
			followRedirects = res[4].(bool)
		}
	case method == "POST" || method == "PUT" || method == "PATCH":
		if len(res) > 1 {
			body = bytes.NewBuffer(res[1].([]byte))
		}
		if len(res) > 2 {
			timeout = parseTimeout(res[2])
		}
		if len(res) > 3 {
			cookies = parseCookies(res[3].(map[string]interface{}))
		}
		if len(res) > 4 {
			headers = parseHeaders(res[4].(map[string]interface{}))
		}
		if len(res) > 5 {
			followRedirects = res[5].(bool)
		}
	}

	request = &Request{method: method, url: url, timeout: timeout,
		followRedirects: followRedirects,
		cookies:         cookies, headers: headers}
	if body != nil {
		request.body = body
	}
	return request
}

func (gofetcher *Gofetcher) writeResponse(response *cocaine.Response, request *Request, resp *Response, err error) {
	if err != nil {
		response.Write([]interface{}{false, err.Error(), 0, http.Header{}})
		gofetcher.Logger.Err(fmt.Sprintf("Error occured: %v, while downloading %s",
			err.Error(), request.url))
	} else {
		response.Write([]interface{}{true, resp.body, resp.httpResponse.StatusCode, resp.header})
		gofetcher.Logger.Info(fmt.Sprintf("Response code: %d, url: %s, runtime: %v",
			resp.httpResponse.StatusCode, request.url, resp.runtime))
	}
}

func (gofetcher *Gofetcher) handler(method string, request *cocaine.Request, response *cocaine.Response) {
	defer response.Close()
	requestBody := <-request.Read()
	httpRequest := gofetcher.parseRequest(method, requestBody)
	resp, err := gofetcher.performRequest(httpRequest)
	gofetcher.writeResponse(response, httpRequest, resp, err)
}

func (gofetcher *Gofetcher) GetHandler(method string) func(request *cocaine.Request, response *cocaine.Response) {
	return func(request *cocaine.Request, response *cocaine.Response) {
		gofetcher.handler(method, request, response)
	}
}

// Http methods

func (gofetcher *Gofetcher) HttpProxy(res http.ResponseWriter, req *http.Request) {
	var (
		timeout int64 = DefaultTimeout
	)
	url := req.FormValue("url")
	timeoutArg := req.FormValue("timeout")
	if timeoutArg != "" {
		tout, _ := strconv.Atoi(timeoutArg)
		timeout = int64(tout)
	}
	httpRequest := Request{method: req.Method, url: url, timeout: timeout,
		followRedirects: DefaultFollowRedirects, headers: req.Header, body: req.Body}
	resp, err := gofetcher.performRequest(&httpRequest)
	if err != nil {
		res.Header().Set("Content-Type", "text/html")
		res.WriteHeader(500)
		res.Write([]byte(err.Error()))
		gofetcher.Logger.Err("Gofetcher error: " + err.Error())

	} else {
		for key, values := range resp.header {
			for _, value := range values {
				res.Header().Add(key, value)
			}
		}
		res.WriteHeader(200)
		if _, err := res.Write(resp.body); err != nil {
			gofetcher.Logger.Errf("Error: %v", err)
		}
	}
}

func (gofetcher *Gofetcher) HttpEcho(res http.ResponseWriter, req *http.Request) {
	gofetcher.Logger.Info("Http echo handler requested")
	text := req.FormValue("text")
	res.Header().Set("Content-Type", "text/html")
	res.WriteHeader(200)
	res.Write([]byte(text))
}
<commit_msg>Fix EOF problem with keep-alive connections. Fix redirect response.<commit_after>package gofetcher

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"time"

	"github.com/cocaine/cocaine-framework-go/cocaine"
	"github.com/ugorji/go/codec"
)

const (
	DefaultTimeout         = 5000
	DefaultFollowRedirects = true
)

type Gofetcher struct {
	Logger *cocaine.Logger
	Transport *http.Transport
}

type Cookies map[string]string

type Request struct {
	method          string
	url             string
	body            io.Reader
	timeout         int64
	cookies         Cookies
	headers         http.Header
	followRedirects bool
}

type responseAndError struct {
	res *http.Response
	err error
}

type Response struct {
	httpResponse *http.Response
	body         []byte
	header       http.Header
	runtime      time.Duration
}

func NewGofetcher() *Gofetcher {
	logger, err := cocaine.NewLogger()
	if err != nil {
		fmt.Printf("Could not initialize logger due to error: %v", err)
		return nil
	}
	transport := &http.Transport{Proxy: http.ProxyFromEnvironment}
	gofetcher := Gofetcher{logger, transport}
	return &gofetcher
}

func noRedirect(_ *http.Request, via []*http.Request) error {
	if len(via) > 0 {
		return errors.New("stopped after first redirect")
	}
	return nil
}

func (gofetcher *Gofetcher) performRequest(request *Request, attempt int) (*Response, error) {
	var (
		err            error
		httpRequest    *http.Request
		httpResponse   *http.Response
		requestTimeout time.Duration = time.Duration(request.timeout) * time.Millisecond
	)
	gofetcher.Logger.Info(fmt.Sprintf("Requested url: %s, method: %s, timeout: %d, headers: %v, attempt: %d",
		request.url, request.method, request.timeout, request.headers, attempt))
	httpClient := &http.Client{Transport: gofetcher.Transport}
	if request.followRedirects == false {
		httpClient.CheckRedirect = noRedirect
	}
	httpRequest, err = http.NewRequest(request.method, request.url, request.body)
	if err != nil {
		return nil, err
	}
	for name, value := range request.cookies {
		httpRequest.AddCookie(&http.Cookie{Name: name, Value: value})
	}
	httpRequest.Header = request.headers
	resultChan := make(chan responseAndError)
	started := time.Now()
	go func() {
		res, err := httpClient.Do(httpRequest)
		resultChan <- responseAndError{res, err}
	}()
	// http connection stay active after timeout exceeded, cause we can't close it using current client api.
	// Read more about timeouts: https://code.google.com/p/go/issues/detail?id=3362
	//
	select {
	case result := <-resultChan:
		httpResponse, err = result.res, result.err
	case <-time.After(requestTimeout):
		err = errors.New(fmt.Sprintf("Request timeout[%s] exceeded", requestTimeout.String()))
		go func() {
			// close httpResponse when it ready
			result := <-resultChan
			if result.res != nil {
				result.res.Body.Close()
			}
		}()
	}
	body := []byte{}
	if err != nil {
		// special case for redirect failure (returns both response and error)
		// read more https://code.google.com/p/go/issues/detail?id=3795
		if httpResponse == nil {
			if urlError, ok := err.(*url.Error); ok {
				// golang bug: golang.org/issue/3514
				if urlError.Err == io.EOF {
					gofetcher.Logger.Info(fmt.Sprintf("Got EOF error while loading %s, attempt(%d)", request.url, attempt))
					if attempt == 1 {
						return gofetcher.performRequest(request, attempt + 1)
					}
				}
			}
			return nil, err
		}
	} else {
		defer httpResponse.Body.Close()
		body, err = ioutil.ReadAll(httpResponse.Body)
		if err != nil {
			return nil, err
		}
	}
	runtime := time.Since(started)
	response := &Response{httpResponse: httpResponse, body: body, header: httpResponse.Header, runtime: runtime}
	return response, nil

}

// Normal methods

func parseHeaders(rawHeaders map[string]interface{}) http.Header {
	headers := make(http.Header)
	for name, values := range rawHeaders {
		for _, value := range values.([]interface{}) {
			headers.Add(name, string(value.([]uint8))) // to transform in canonical form
		}
	}
	return headers
}

func parseCookies(rawCookie map[string]interface{}) Cookies {
	cookies := Cookies{}
	for key, value := range rawCookie {
		cookies[key] = string(value.([]uint8))
	}
	return cookies
}

func parseTimeout(rawTimeout interface{}) (timeout int64) {
	// is it possible to got timeout in int64 instead of uint64?
	switch rawTimeout.(type) {
	case uint64:
		timeout = int64(rawTimeout.(uint64))
	case int64:
		timeout = rawTimeout.(int64)
	}
	return timeout
}

func (gofetcher *Gofetcher) parseRequest(method string, requestBody []byte) (request *Request) {
	var (
		mh              codec.MsgpackHandle
		h                     = &mh
		timeout         int64 = DefaultTimeout
		cookies         Cookies
		headers              = make(http.Header)
		followRedirects bool = DefaultFollowRedirects
		body            *bytes.Buffer
	)
	mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
	var res []interface{}
	codec.NewDecoderBytes(requestBody, h).Decode(&res)
	url := string(res[0].([]uint8))
	switch {
	case method == "GET" || method == "HEAD" || method == "DELETE":
		if len(res) > 1 {
			timeout = parseTimeout(res[1])
		}
		if len(res) > 2 {
			cookies = parseCookies(res[2].(map[string]interface{}))
		}
		if len(res) > 3 {
			headers = parseHeaders(res[3].(map[string]interface{}))
		}
		if len(res) > 4 {
			followRedirects = res[4].(bool)
		}
	case method == "POST" || method == "PUT" || method == "PATCH":
		if len(res) > 1 {
			body = bytes.NewBuffer(res[1].([]byte))
		}
		if len(res) > 2 {
			timeout = parseTimeout(res[2])
		}
		if len(res) > 3 {
			cookies = parseCookies(res[3].(map[string]interface{}))
		}
		if len(res) > 4 {
			headers = parseHeaders(res[4].(map[string]interface{}))
		}
		if len(res) > 5 {
			followRedirects = res[5].(bool)
		}
	}

	request = &Request{method: method, url: url, timeout: timeout,
		followRedirects: followRedirects,
		cookies:         cookies, headers: headers}
	if body != nil {
		request.body = body
	}
	return request
}

func (gofetcher *Gofetcher) writeResponse(response *cocaine.Response, request *Request, resp *Response, err error) {
	if err != nil {
		response.Write([]interface{}{false, err.Error(), 0, http.Header{}})
		gofetcher.Logger.Err(fmt.Sprintf("Error occured: %v, while downloading %s",
			err.Error(), request.url))
	} else {
		response.Write([]interface{}{true, resp.body, resp.httpResponse.StatusCode, resp.header})
		gofetcher.Logger.Info(fmt.Sprintf("Response code: %d, url: %s, runtime: %v",
			resp.httpResponse.StatusCode, request.url, resp.runtime))
	}
}

func (gofetcher *Gofetcher) handler(method string, request *cocaine.Request, response *cocaine.Response) {
	defer response.Close()
	requestBody := <-request.Read()
	httpRequest := gofetcher.parseRequest(method, requestBody)
	resp, err := gofetcher.performRequest(httpRequest, 1)
	gofetcher.writeResponse(response, httpRequest, resp, err)
}

func (gofetcher *Gofetcher) GetHandler(method string) func(request *cocaine.Request, response *cocaine.Response) {
	return func(request *cocaine.Request, response *cocaine.Response) {
		gofetcher.handler(method, request, response)
	}
}

// Http methods

func (gofetcher *Gofetcher) HttpProxy(res http.ResponseWriter, req *http.Request) {
	var (
		timeout int64 = DefaultTimeout
	)
	url := req.FormValue("url")
	timeoutArg := req.FormValue("timeout")
	if timeoutArg != "" {
		tout, _ := strconv.Atoi(timeoutArg)
		timeout = int64(tout)
	}
	httpRequest := Request{method: req.Method, url: url, timeout: timeout,
		followRedirects: DefaultFollowRedirects, headers: req.Header, body: req.Body}
	resp, err := gofetcher.performRequest(&httpRequest, 1)
	if err != nil {
		res.Header().Set("Content-Type", "text/html")
		res.WriteHeader(500)
		res.Write([]byte(err.Error()))
		gofetcher.Logger.Err("Gofetcher error: " + err.Error())

	} else {
		for key, values := range resp.header {
			for _, value := range values {
				res.Header().Add(key, value)
			}
		}
		res.WriteHeader(200)
		if _, err := res.Write(resp.body); err != nil {
			gofetcher.Logger.Errf("Error: %v", err)
		}
	}
}

func (gofetcher *Gofetcher) HttpEcho(res http.ResponseWriter, req *http.Request) {
	gofetcher.Logger.Info("Http echo handler requested")
	text := req.FormValue("text")
	res.Header().Set("Content-Type", "text/html")
	res.WriteHeader(200)
	res.Write([]byte(text))
}
<|endoftext|>


<commit_before>// Copyright 2013 Unknown
//
// 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 zip enables you to transparently read or write ZIP compressed archives and the files inside them.
package zip

import (
	"archive/zip"
	"errors"
	"io"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/Unknwon/cae"
)

// A File represents a file or directory entry in archive.
type File struct {
	*zip.FileHeader
	oldName    string // NOTE: unused, for future change name feature.
	oldComment string // NOTE: unused, for future change comment feature.
	absPath    string // Absolute path of local file system.
	tmpPath    string
}

// A ZipArchive represents a file archive, compressed with Zip.
type ZipArchive struct {
	*zip.ReadCloser
	FileName   string
	Comment    string
	NumFiles   int
	Flag       int
	Permission os.FileMode

	files        []*File
	isHasChanged bool

	// For supporting flushing to io.Writer.
	writer      io.Writer
	isHasWriter bool
}

// OpenFile is the generalized open call; most users will use Open
// instead. It opens the named zip file with specified flag
// (O_RDONLY etc.) if applicable. If successful,
// methods on the returned ZipArchive can be used for I/O.
// If there is an error, it will be of type *PathError.
func OpenFile(name string, flag int, perm os.FileMode) (*ZipArchive, error) {
	z := new(ZipArchive)
	err := z.Open(name, flag, perm)
	return z, err
}

// Create creates the named zip file, truncating
// it if it already exists. If successful, methods on the returned
// ZipArchive can be used for I/O; the associated file descriptor has mode
// O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*ZipArchive, error) {
	os.MkdirAll(path.Dir(name), os.ModePerm)
	return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
}

// Open opens the named zip file for reading. If successful, methods on
// the returned ZipArchive can be used for reading; the associated file
// descriptor has mode O_RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (*ZipArchive, error) {
	return OpenFile(name, os.O_RDONLY, 0)
}

// New accepts a variable that implemented interface io.Writer
// for write-only purpose operations.
func New(w io.Writer) *ZipArchive {
	return &ZipArchive{
		writer:      w,
		isHasWriter: true,
	}
}

// List returns a string slice of files' name in ZipArchive.
// Specify prefixes will be used as filters.
func (z *ZipArchive) List(prefixes ...string) []string {
	isHasPrefix := len(prefixes) > 0
	names := make([]string, 0, z.NumFiles)
	for _, f := range z.files {
		if isHasPrefix && !cae.HasPrefix(f.Name, prefixes) {
			continue
		}
		names = append(names, f.Name)
	}
	return names
}

// AddEmptyDir adds a raw directory entry to ZipArchive,
// it returns false if same directory enry already existed.
func (z *ZipArchive) AddEmptyDir(dirPath string) bool {
	if !strings.HasSuffix(dirPath, "/") {
		dirPath += "/"
	}

	for _, f := range z.files {
		if dirPath == f.Name {
			return false
		}
	}

	dirPath = strings.TrimSuffix(dirPath, "/")
	if strings.Contains(dirPath, "/") {
		// Auto add all upper level directories.
		z.AddEmptyDir(path.Dir(dirPath))
	}
	z.files = append(z.files, &File{
		FileHeader: &zip.FileHeader{
			Name:             dirPath + "/",
			UncompressedSize: 0,
		},
	})
	z.updateStat()
	return true
}

// AddDir adds a directory and subdirectories entries to ZipArchive.
func (z *ZipArchive) AddDir(dirPath, absPath string) error {
	dir, err := os.Open(absPath)
	if err != nil {
		return err
	}
	defer dir.Close()

	// Make sure we have all upper level directories.
	z.AddEmptyDir(dirPath)

	fis, err := dir.Readdir(0)
	if err != nil {
		return err
	}
	for _, fi := range fis {
		curPath := strings.Replace(absPath+"/"+fi.Name(), "\\", "/", -1)
		tmpRecPath := strings.Replace(filepath.Join(dirPath, fi.Name()), "\\", "/", -1)
		if fi.IsDir() {
			if err = z.AddDir(tmpRecPath, curPath); err != nil {
				return err
			}
		} else {
			if err = z.AddFile(tmpRecPath, curPath); err != nil {
				return err
			}
		}
	}
	return nil
}

// updateStat should be called after every change for rebuilding statistic.
func (z *ZipArchive) updateStat() {
	z.NumFiles = len(z.files)
	z.isHasChanged = true
}

// AddFile adds a file entry to ZipArchive.
func (z *ZipArchive) AddFile(fileName, absPath string) error {
	if cae.IsFilter(absPath) {
		return nil
	}

	f, err := os.Open(absPath)
	if err != nil {
		return err
	}
	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		return err
	}

	file := new(File)
	file.FileHeader, err = zip.FileInfoHeader(fi)
	if err != nil {
		return err
	}
	file.Name = fileName
	file.absPath = absPath

	z.AddEmptyDir(path.Dir(fileName))

	isExist := false
	for _, f := range z.files {
		if fileName == f.Name {
			f = file
			isExist = true
			break
		}
	}
	if !isExist {
		z.files = append(z.files, file)
	}

	z.updateStat()
	return nil
}

// DeleteIndex deletes an entry in the archive by its index.
func (z *ZipArchive) DeleteIndex(idx int) error {
	if idx >= z.NumFiles {
		return errors.New("index out of range of number of files")
	}

	z.files = append(z.files[:idx], z.files[idx+1:]...)
	return nil
}

// DeleteName deletes an entry in the archive by its name.
func (z *ZipArchive) DeleteName(name string) error {
	for i, f := range z.files {
		if f.Name == name {
			return z.DeleteIndex(i)
		}
	}
	return errors.New("entry with given name not found")
}
<commit_msg>gogits/gogs#3216 convert Windows-styled directory delimiters to unix format<commit_after>// Copyright 2013 Unknown
//
// 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 zip enables you to transparently read or write ZIP compressed archives and the files inside them.
package zip

import (
	"archive/zip"
	"errors"
	"io"
	"os"
	"path"
	"strings"

	"github.com/Unknwon/cae"
)

// A File represents a file or directory entry in archive.
type File struct {
	*zip.FileHeader
	oldName    string // NOTE: unused, for future change name feature.
	oldComment string // NOTE: unused, for future change comment feature.
	absPath    string // Absolute path of local file system.
	tmpPath    string
}

// A ZipArchive represents a file archive, compressed with Zip.
type ZipArchive struct {
	*zip.ReadCloser
	FileName   string
	Comment    string
	NumFiles   int
	Flag       int
	Permission os.FileMode

	files        []*File
	isHasChanged bool

	// For supporting flushing to io.Writer.
	writer      io.Writer
	isHasWriter bool
}

// OpenFile is the generalized open call; most users will use Open
// instead. It opens the named zip file with specified flag
// (O_RDONLY etc.) if applicable. If successful,
// methods on the returned ZipArchive can be used for I/O.
// If there is an error, it will be of type *PathError.
func OpenFile(name string, flag int, perm os.FileMode) (*ZipArchive, error) {
	z := new(ZipArchive)
	err := z.Open(name, flag, perm)
	return z, err
}

// Create creates the named zip file, truncating
// it if it already exists. If successful, methods on the returned
// ZipArchive can be used for I/O; the associated file descriptor has mode
// O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*ZipArchive, error) {
	os.MkdirAll(path.Dir(name), os.ModePerm)
	return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
}

// Open opens the named zip file for reading. If successful, methods on
// the returned ZipArchive can be used for reading; the associated file
// descriptor has mode O_RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (*ZipArchive, error) {
	return OpenFile(name, os.O_RDONLY, 0)
}

// New accepts a variable that implemented interface io.Writer
// for write-only purpose operations.
func New(w io.Writer) *ZipArchive {
	return &ZipArchive{
		writer:      w,
		isHasWriter: true,
	}
}

// List returns a string slice of files' name in ZipArchive.
// Specify prefixes will be used as filters.
func (z *ZipArchive) List(prefixes ...string) []string {
	isHasPrefix := len(prefixes) > 0
	names := make([]string, 0, z.NumFiles)
	for _, f := range z.files {
		if isHasPrefix && !cae.HasPrefix(f.Name, prefixes) {
			continue
		}
		names = append(names, f.Name)
	}
	return names
}

// AddEmptyDir adds a raw directory entry to ZipArchive,
// it returns false if same directory enry already existed.
func (z *ZipArchive) AddEmptyDir(dirPath string) bool {
	dirPath = strings.Replace(dirPath, "\\", "/", -1)

	if !strings.HasSuffix(dirPath, "/") {
		dirPath += "/"
	}

	for _, f := range z.files {
		if dirPath == f.Name {
			return false
		}
	}

	dirPath = strings.TrimSuffix(dirPath, "/")
	if strings.Contains(dirPath, "/") {
		// Auto add all upper level directories.
		z.AddEmptyDir(path.Dir(dirPath))
	}
	z.files = append(z.files, &File{
		FileHeader: &zip.FileHeader{
			Name:             dirPath + "/",
			UncompressedSize: 0,
		},
	})
	z.updateStat()
	return true
}

// AddDir adds a directory and subdirectories entries to ZipArchive.
func (z *ZipArchive) AddDir(dirPath, absPath string) error {
	dir, err := os.Open(absPath)
	if err != nil {
		return err
	}
	defer dir.Close()

	// Make sure we have all upper level directories.
	z.AddEmptyDir(dirPath)

	fis, err := dir.Readdir(0)
	if err != nil {
		return err
	}
	for _, fi := range fis {
		curPath := absPath + "/" + fi.Name()
		tmpRecPath := path.Join(dirPath, fi.Name())
		if fi.IsDir() {
			if err = z.AddDir(tmpRecPath, curPath); err != nil {
				return err
			}
		} else {
			if err = z.AddFile(tmpRecPath, curPath); err != nil {
				return err
			}
		}
	}
	return nil
}

// updateStat should be called after every change for rebuilding statistic.
func (z *ZipArchive) updateStat() {
	z.NumFiles = len(z.files)
	z.isHasChanged = true
}

// AddFile adds a file entry to ZipArchive.
func (z *ZipArchive) AddFile(fileName, absPath string) error {
	fileName = strings.Replace(fileName, "\\", "/", -1)
	absPath = strings.Replace(absPath, "\\", "/", -1)

	if cae.IsFilter(absPath) {
		return nil
	}

	f, err := os.Open(absPath)
	if err != nil {
		return err
	}
	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		return err
	}

	file := new(File)
	file.FileHeader, err = zip.FileInfoHeader(fi)
	if err != nil {
		return err
	}
	file.Name = fileName
	file.absPath = absPath

	z.AddEmptyDir(path.Dir(fileName))

	isExist := false
	for _, f := range z.files {
		if fileName == f.Name {
			f = file
			isExist = true
			break
		}
	}
	if !isExist {
		z.files = append(z.files, file)
	}

	z.updateStat()
	return nil
}

// DeleteIndex deletes an entry in the archive by its index.
func (z *ZipArchive) DeleteIndex(idx int) error {
	if idx >= z.NumFiles {
		return errors.New("index out of range of number of files")
	}

	z.files = append(z.files[:idx], z.files[idx+1:]...)
	return nil
}

// DeleteName deletes an entry in the archive by its name.
func (z *ZipArchive) DeleteName(name string) error {
	for i, f := range z.files {
		if f.Name == name {
			return z.DeleteIndex(i)
		}
	}
	return errors.New("entry with given name not found")
}
<|endoftext|>


<commit_before>package ziputil

import (
	"archive/zip"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
)

type ZipFile struct {
	writer *zip.Writer
}

func Create(filename string) (*ZipFile, error) {
	file, err := os.Create(filename)
	if err != nil {
		return nil, err
	}
	return &ZipFile{writer: zip.NewWriter(file)}, nil
}

func (z *ZipFile) Close() error {
	return z.writer.Close()
}

func (z *ZipFile) AddEntryN(path string, names ...string) error {
	for _, name := range names {
		zipPath := filepath.Join(path, name)
		err := z.AddEntry(zipPath, name)
		if err != nil {
			return err
		}
	}
	return nil
}

func (z *ZipFile) AddEntry(path, name string) error {
	fi, err := os.Stat(name)
	if err != nil {
		return err
	}

	fh, err := zip.FileInfoHeader(fi)
	if err != nil {
		return err
	}

	fh.Name = path

	entry, err := z.writer.CreateHeader(fh)
	if err != nil {
		return err
	}

	if fi.IsDir() {
		return nil
	}

	file, err := os.Open(name)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = io.Copy(entry, file)

	return err
}

func (z *ZipFile) AddDirectoryN(path string, names ...string) error {
	for _, name := range names {
		err := z.AddDirectory(path, name)
		if err != nil {
			return err
		}
	}
	return nil
}

func (z *ZipFile) AddDirectory(path, dirName string) error {
	z.AddEntry(path, dirName)

	files, err := ioutil.ReadDir(dirName)
	if err != nil {
		return err
	}

	for _, file := range files {
		localPath := filepath.Join(dirName, file.Name())
		zipPath := filepath.Join(path, file.Name())

		err = nil
		if file.IsDir() {
			err = z.AddDirectory(zipPath, localPath)
		} else {
			err = z.AddEntry(zipPath, localPath)
		}
		if err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>fixed to not create empty directory<commit_after>package ziputil

import (
	"archive/zip"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
)

type ZipFile struct {
	writer *zip.Writer
}

func Create(filename string) (*ZipFile, error) {
	file, err := os.Create(filename)
	if err != nil {
		return nil, err
	}
	return &ZipFile{writer: zip.NewWriter(file)}, nil
}

func (z *ZipFile) Close() error {
	return z.writer.Close()
}

func (z *ZipFile) AddEntryN(path string, names ...string) error {
	for _, name := range names {
		zipPath := filepath.Join(path, name)
		err := z.AddEntry(zipPath, name)
		if err != nil {
			return err
		}
	}
	return nil
}

func (z *ZipFile) AddEntry(path, name string) error {
	fi, err := os.Stat(name)
	if err != nil {
		return err
	}

	fh, err := zip.FileInfoHeader(fi)
	if err != nil {
		return err
	}

	fh.Name = path

	entry, err := z.writer.CreateHeader(fh)
	if err != nil {
		return err
	}

	if fi.IsDir() {
		return nil
	}

	file, err := os.Open(name)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = io.Copy(entry, file)

	return err
}

func (z *ZipFile) AddDirectoryN(path string, names ...string) error {
	for _, name := range names {
		err := z.AddDirectory(path, name)
		if err != nil {
			return err
		}
	}
	return nil
}

func (z *ZipFile) AddDirectory(path, dirName string) error {
	files, err := ioutil.ReadDir(dirName)
	if err != nil {
		return err
	}

	for _, file := range files {
		localPath := filepath.Join(dirName, file.Name())
		zipPath := filepath.Join(path, file.Name())

		err = nil
		if file.IsDir() {
			z.AddEntry(path, dirName)
			err = z.AddDirectory(zipPath, localPath)
		} else {
			err = z.AddEntry(zipPath, localPath)
		}
		if err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Vastri. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package scanner

import (
	"testing"

	"github.com/vastri/zolang/token"
)

const (
	special = iota
	literal
	operator
)

func tokenclass(tok token.Token) int {
	switch {
	case tok.IsLiteral():
		return literal
	case tok.IsOperator():
		return operator
	}
	return special
}

type elt struct {
	tok   token.Token
	lit   string
	class int
}

var tokens = [...]elt{
	// Special tokens.
	{token.COMMENT, "/* a comment */", special},
	{token.COMMENT, "// a comment \n", special},
	{token.COMMENT, "/*\r*/", special},
	{token.COMMENT, "//\r\n", special},

	// Identifiers and basic type literals.
	{token.IDENT, "foobar", literal},
	{token.IDENT, "a۰۱۸", literal},
	{token.IDENT, "foo६४", literal},
	{token.IDENT, "bar９８７６", literal},
	{token.IDENT, "ŝ", literal},
	{token.IDENT, "ŝfoo", literal},
	{token.BOOL, "true", literal},
	{token.BOOL, "false", literal},
	{token.INT, "0", literal},
	{token.INT, "1", literal},
	{token.INT, "123456789012345678890", literal},
	{token.INT, "01234567", literal},
	{token.INT, "0xcafebabe", literal},
	{token.FLOAT, "0.", literal},
	{token.FLOAT, ".0", literal},
	{token.FLOAT, "3.14159265", literal},
	{token.FLOAT, "1e0", literal},
	{token.FLOAT, "1e+100", literal},
	{token.FLOAT, "1e-100", literal},
	{token.FLOAT, "2.71828e-1000", literal},
	{token.STRING, "\"\"", literal},
	{token.STRING, "\"a\"", literal},
	{token.STRING, "\"foobar\"", literal},
	{token.STRING, "\"${v}\"", literal},
	{token.STRING, "\"foo${v}bar\"", literal},
	{token.RAWSTRING, "''", literal},
	{token.RAWSTRING, "'a'", literal},
	{token.RAWSTRING, "'foobar'", literal},
	{token.RAWSTRING, "'${v}'", literal},
	{token.RAWSTRING, "'foo${v}bar'", literal},

	// Operators and delimiters
	{token.ADD, "+", operator},
	{token.SUB, "-", operator},
	{token.MUL, "*", operator},
	{token.QUO, "/", operator},
	{token.REM, "%", operator},

	{token.AND, "&&", operator},
	{token.OR, "||", operator},

	{token.EQL, "==", operator},
	{token.LSS, "<", operator},
	{token.GTR, ">", operator},
	{token.ASSIGN, "=", operator},
	{token.NOT, "!", operator},

	{token.NEQ, "!=", operator},
	{token.LEQ, "<=", operator},
	{token.GEQ, ">=", operator},

	{token.LPAREN, "(", operator},
	{token.LBRACK, "[", operator},
	{token.LBRACE, "{", operator},
	{token.COMMA, ",", operator},
	{token.PERIOD, ".", operator},

	{token.RPAREN, ")", operator},
	{token.RBRACK, "]", operator},
	{token.RBRACE, "}", operator},
	{token.COLON, ":", operator},
}

const whitespace = "  \t  \n\n\n"

var source = func() []byte {
	var src []byte
	for _, t := range tokens {
		src = append(src, t.lit...)
		src = append(src, whitespace...)
	}
	return src
}()

func newlineCount(s string) int {
	n := 0
	for i := 0; i < len(s); i++ {
		if s[i] == '\n' {
			n++
		}
	}
	return n
}

func checkPos(t *testing.T, fset *token.FileSet, lit string, p token.Pos, expected token.Position) {
	pos := fset.Position(p)
	if pos.Filename != expected.Filename {
		t.Errorf("bad filename for %q: got %s, expected %s", lit, pos.Filename, expected.Filename)
	}
	if pos.Offset != expected.Offset {
		t.Errorf("bad position for %q: got %d, expected %d", lit, pos.Offset, expected.Offset)
	}
	if pos.Line != expected.Line {
		t.Errorf("bad line for %q: got %d, expected %d", lit, pos.Line, expected.Line)
	}
	if pos.Column != expected.Column {
		t.Errorf("bad column for %q: got %d, expected %d", lit, pos.Column, expected.Column)
	}
}

// TestScan verifies that calling Scan() provides the correct results.
func TestScan(t *testing.T) {
	fset := token.NewFileSet()

	whitespace_linecount := newlineCount(whitespace)

	// Error handler.
	eh := func(_ token.Position, msg string) {
		t.Errorf("error handler called (msg = %s)", msg)
	}

	// Verify scan.
	var s Scanner
	s.Init(fset.AddFile("", fset.Base(), len(source)), source, eh)

	// Set up expected position.
	epos := token.Position{
		Filename: "",
		Offset:   0,
		Line:     1,
		Column:   1,
	}

	index := 0
	for {
		pos, tok, lit := s.Scan()

		// Check position.
		if tok == token.EOF {
			// Correction for EOF.
			epos.Line = newlineCount(string(source))
			epos.Column = 2
		}
		checkPos(t, fset, lit, pos, epos)

		// Check token.
		e := elt{token.EOF, "", special}
		if index < len(tokens) {
			e = tokens[index]
			index++
		}
		if tok != e.tok {
			t.Errorf("bad token for %q: got %s, expected %s", e.lit, tok, e.tok)
		}

		// Check token class.
		if tokenclass(tok) != e.class {
			t.Errorf("bad class for %q: got %d, expected %d", e.lit, tokenclass(tok), e.class)
		}

		// Check literal.
		elit := ""
		if tok.IsLiteral() {
			elit = e.lit
		}
		if lit != elit {
			t.Errorf("bad literal for %q: got %q, expected %q", e.lit, lit, elit)
		}

		if tok == token.EOF {
			break
		}

		// Update position.
		epos.Offset += len(e.lit) + len(whitespace)
		epos.Line += newlineCount(e.lit) + whitespace_linecount
	}

	if s.ErrorCount != 0 {
		t.Errorf("found %d errors", s.ErrorCount)
	}
}
<commit_msg>Scanner: Add benchmark for Scan<commit_after>// Copyright 2016 Vastri. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package scanner

import (
	"testing"

	"github.com/vastri/zolang/token"
)

const (
	special = iota
	literal
	operator
)

func tokenclass(tok token.Token) int {
	switch {
	case tok.IsLiteral():
		return literal
	case tok.IsOperator():
		return operator
	}
	return special
}

type elt struct {
	tok   token.Token
	lit   string
	class int
}

var tokens = [...]elt{
	// Special tokens.
	{token.COMMENT, "/* a comment */", special},
	{token.COMMENT, "// a comment \n", special},
	{token.COMMENT, "/*\r*/", special},
	{token.COMMENT, "//\r\n", special},

	// Identifiers and basic type literals.
	{token.IDENT, "foobar", literal},
	{token.IDENT, "a۰۱۸", literal},
	{token.IDENT, "foo६४", literal},
	{token.IDENT, "bar９８７６", literal},
	{token.IDENT, "ŝ", literal},
	{token.IDENT, "ŝfoo", literal},
	{token.BOOL, "true", literal},
	{token.BOOL, "false", literal},
	{token.INT, "0", literal},
	{token.INT, "1", literal},
	{token.INT, "123456789012345678890", literal},
	{token.INT, "01234567", literal},
	{token.INT, "0xcafebabe", literal},
	{token.FLOAT, "0.", literal},
	{token.FLOAT, ".0", literal},
	{token.FLOAT, "3.14159265", literal},
	{token.FLOAT, "1e0", literal},
	{token.FLOAT, "1e+100", literal},
	{token.FLOAT, "1e-100", literal},
	{token.FLOAT, "2.71828e-1000", literal},
	{token.STRING, "\"\"", literal},
	{token.STRING, "\"a\"", literal},
	{token.STRING, "\"foobar\"", literal},
	{token.STRING, "\"${v}\"", literal},
	{token.STRING, "\"foo${v}bar\"", literal},
	{token.RAWSTRING, "''", literal},
	{token.RAWSTRING, "'a'", literal},
	{token.RAWSTRING, "'foobar'", literal},
	{token.RAWSTRING, "'${v}'", literal},
	{token.RAWSTRING, "'foo${v}bar'", literal},

	// Operators and delimiters
	{token.ADD, "+", operator},
	{token.SUB, "-", operator},
	{token.MUL, "*", operator},
	{token.QUO, "/", operator},
	{token.REM, "%", operator},

	{token.AND, "&&", operator},
	{token.OR, "||", operator},

	{token.EQL, "==", operator},
	{token.LSS, "<", operator},
	{token.GTR, ">", operator},
	{token.ASSIGN, "=", operator},
	{token.NOT, "!", operator},

	{token.NEQ, "!=", operator},
	{token.LEQ, "<=", operator},
	{token.GEQ, ">=", operator},

	{token.LPAREN, "(", operator},
	{token.LBRACK, "[", operator},
	{token.LBRACE, "{", operator},
	{token.COMMA, ",", operator},
	{token.PERIOD, ".", operator},

	{token.RPAREN, ")", operator},
	{token.RBRACK, "]", operator},
	{token.RBRACE, "}", operator},
	{token.COLON, ":", operator},
}

const whitespace = "  \t  \n\n\n"

var source = func() []byte {
	var src []byte
	for _, t := range tokens {
		src = append(src, t.lit...)
		src = append(src, whitespace...)
	}
	return src
}()

func newlineCount(s string) int {
	n := 0
	for i := 0; i < len(s); i++ {
		if s[i] == '\n' {
			n++
		}
	}
	return n
}

func checkPos(t *testing.T, fset *token.FileSet, lit string, p token.Pos, expected token.Position) {
	pos := fset.Position(p)
	if pos.Filename != expected.Filename {
		t.Errorf("bad filename for %q: got %s, expected %s", lit, pos.Filename, expected.Filename)
	}
	if pos.Offset != expected.Offset {
		t.Errorf("bad position for %q: got %d, expected %d", lit, pos.Offset, expected.Offset)
	}
	if pos.Line != expected.Line {
		t.Errorf("bad line for %q: got %d, expected %d", lit, pos.Line, expected.Line)
	}
	if pos.Column != expected.Column {
		t.Errorf("bad column for %q: got %d, expected %d", lit, pos.Column, expected.Column)
	}
}

// TestScan verifies that calling Scan() provides the correct results.
func TestScan(t *testing.T) {
	fset := token.NewFileSet()

	whitespace_linecount := newlineCount(whitespace)

	// Error handler.
	eh := func(_ token.Position, msg string) {
		t.Errorf("error handler called (msg = %s)", msg)
	}

	// Verify scan.
	var s Scanner
	s.Init(fset.AddFile("", fset.Base(), len(source)), source, eh)

	// Set up expected position.
	epos := token.Position{
		Filename: "",
		Offset:   0,
		Line:     1,
		Column:   1,
	}

	index := 0
	for {
		pos, tok, lit := s.Scan()

		// Check position.
		if tok == token.EOF {
			// Correction for EOF.
			epos.Line = newlineCount(string(source))
			epos.Column = 2
		}
		checkPos(t, fset, lit, pos, epos)

		// Check token.
		e := elt{token.EOF, "", special}
		if index < len(tokens) {
			e = tokens[index]
			index++
		}
		if tok != e.tok {
			t.Errorf("bad token for %q: got %s, expected %s", e.lit, tok, e.tok)
		}

		// Check token class.
		if tokenclass(tok) != e.class {
			t.Errorf("bad class for %q: got %d, expected %d", e.lit, tokenclass(tok), e.class)
		}

		// Check literal.
		elit := ""
		if tok.IsLiteral() {
			elit = e.lit
		}
		if lit != elit {
			t.Errorf("bad literal for %q: got %q, expected %q", e.lit, lit, elit)
		}

		if tok == token.EOF {
			break
		}

		// Update position.
		epos.Offset += len(e.lit) + len(whitespace)
		epos.Line += newlineCount(e.lit) + whitespace_linecount
	}

	if s.ErrorCount != 0 {
		t.Errorf("found %d errors", s.ErrorCount)
	}
}

func BenchmarkScan(b *testing.B) {
	b.StopTimer()
	fset := token.NewFileSet()
	file := fset.AddFile("", fset.Base(), len(source))
	var s Scanner
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		s.Init(file, source, nil)
		for {
			_, tok, _ := s.Scan()
			if tok == token.EOF {
				break
			}
		}
	}
}
<|endoftext|>