<commit_before>// Copyright 2017 The ACH Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

package ach

import (
	"strings"
	"testing"
)

func mockBatchControl() *BatchControl {
	bc := NewBatchControl()
	bc.ServiceClassCode = 220
	bc.CompanyIdentification = "121042882"
	bc.ODFIIdentification = "12104288"
	return bc
}

func TestMockBatchControl(t *testing.T) {
	bc := mockBatchControl()
	if err := bc.Validate(); err != nil {
		t.Error("mockBatchControl does not validate and will break other tests")
	}
	if bc.ServiceClassCode != 220 {
		t.Error("ServiceClassCode depedendent default value has changed")
	}
	if bc.CompanyIdentification != "121042882" {
		t.Error("CompanyIdentification depedendent default value has changed")
	}
	if bc.ODFIIdentification != "12104288" {
		t.Error("ODFIIdentification depedendent default value has changed")
	}
}

// TestParseBatchControl parses a known Batch ControlRecord string.
func TestParseBatchControl(t *testing.T) {
	var line = "82250000010005320001000000010500000000000000origid                             076401250000001"
	r := NewReader(strings.NewReader(line))
	r.line = line
	bh := BatchHeader{BatchNumber: 1,
		ServiceClassCode:      225,
		CompanyIdentification: "origid",
		ODFIIdentification:    "7640125"}
	r.addCurrentBatch(NewBatchPPD(&bh))

	r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 27, Amount: 10500, RDFIIdentification: "5320001", TraceNumber: 76401255655291})
	if err := r.parseBatchControl(); err != nil {
		t.Errorf("%T: %s", err, err)
	}
	record := r.currentBatch.GetControl()

	if record.recordType != "8" {
		t.Errorf("RecordType Expected '8' got: %v", record.recordType)
	}
	if record.ServiceClassCode != 225 {
		t.Errorf("ServiceClassCode Expected '225' got: %v", record.ServiceClassCode)
	}
	if record.EntryAddendaCountField() != "000001" {
		t.Errorf("EntryAddendaCount Expected '000001' got: %v", record.EntryAddendaCountField())
	}
	if record.EntryHashField() != "0005320001" {
		t.Errorf("EntryHash Expected '0005320001' got: %v", record.EntryHashField())
	}
	if record.TotalDebitEntryDollarAmountField() != "000000010500" {
		t.Errorf("TotalDebitEntryDollarAmount Expected '000000010500' got: %v", record.TotalDebitEntryDollarAmountField())
	}
	if record.TotalCreditEntryDollarAmountField() != "000000000000" {
		t.Errorf("TotalCreditEntryDollarAmount Expected '000000000000' got: %v", record.TotalCreditEntryDollarAmountField())
	}
	if record.CompanyIdentificationField() != "origid    " {
		t.Errorf("CompanyIdentification Expected 'origid    ' got: %v", record.CompanyIdentificationField())
	}
	if record.MessageAuthenticationCodeField() != "                   " {
		t.Errorf("MessageAuthenticationCode Expected '                   ' got: %v", record.MessageAuthenticationCodeField())
	}
	if record.reserved != "      " {
		t.Errorf("Reserved Expected '      ' got: %v", record.reserved)
	}
	if record.ODFIIdentificationField() != "07640125" {
		t.Errorf("OdfiIdentification Expected '07640125' got: %v", record.ODFIIdentificationField())
	}
	if record.BatchNumberField() != "0000001" {
		t.Errorf("BatchNumber Expected '0000001' got: %v", record.BatchNumberField())
	}
}

// TestBCString validats that a known parsed file can be return to a string of the same value
func TestBCString(t *testing.T) {
	var line = "82250000010005320001000000010500000000000000origid                             076401250000001"
	r := NewReader(strings.NewReader(line))
	r.line = line
	bh := BatchHeader{BatchNumber: 1,
		ServiceClassCode:      225,
		CompanyIdentification: "origid",
		ODFIIdentification:    "7640125"}
	r.addCurrentBatch(NewBatchPPD(&bh))

	r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 27, Amount: 10500, RDFIIdentification: "5320001", TraceNumber: 76401255655291})
	if err := r.parseBatchControl(); err != nil {
		t.Errorf("%T: %s", err, err)
	}
	record := r.currentBatch.GetControl()

	if record.String() != line {
		t.Errorf("Strings do not match")
	}
}

// TestValidateBCRecordType ensure error if recordType is not 8
func TestValidateBCRecordType(t *testing.T) {
	bc := mockBatchControl()
	bc.recordType = "2"
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "recordType" {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

func TestBCisServiceClassErr(t *testing.T) {
	bc := mockBatchControl()
	bc.ServiceClassCode = 123
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "ServiceClassCode" {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

func TestBCBatchNumber(t *testing.T) {
	bc := mockBatchControl()
	bc.BatchNumber = 0
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "BatchNumber" {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

func TestBCCompanyIdentificationAlphaNumeric(t *testing.T) {
	bc := mockBatchControl()
	bc.CompanyIdentification = "®"
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "CompanyIdentification" {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

func TestBCMessageAuthenticationCodeAlphaNumeric(t *testing.T) {
	bc := mockBatchControl()
	bc.MessageAuthenticationCode = "®"
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "MessageAuthenticationCode" {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

func TestBCFieldInclusionRecordType(t *testing.T) {
	bc := mockBatchControl()
	bc.recordType = ""
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.Msg != msgFieldInclusion {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

func TestBCFieldInclusionServiceClassCode(t *testing.T) {
	bc := mockBatchControl()
	bc.ServiceClassCode = 0
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.Msg != msgFieldInclusion {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

func TestBCFieldInclusionODFIIdentification(t *testing.T) {
	bc := mockBatchControl()
	bc.ODFIIdentification = "000000000"
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.Msg != msgFieldInclusion {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

func TestBatchControlLength(t *testing.T) {
	bc := NewBatchControl()
	recordLength := len(bc.String())
	if recordLength != 94 {
		t.Errorf("Instantiated length of Batch Control string is not 94 but %v", recordLength)
	}
}
<commit_msg>166 batchControl_internal_test<commit_after>// Copyright 2017 The ACH Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

package ach

import (
	"strings"
	"testing"
)

func mockBatchControl() *BatchControl {
	bc := NewBatchControl()
	bc.ServiceClassCode = 220
	bc.CompanyIdentification = "121042882"
	bc.ODFIIdentification = "12104288"
	return bc
}

// testMockBatchControl tests mock batch control
func testMockBatchControl(t testing.TB) {
	bc := mockBatchControl()
	if err := bc.Validate(); err != nil {
		t.Error("mockBatchControl does not validate and will break other tests")
	}
	if bc.ServiceClassCode != 220 {
		t.Error("ServiceClassCode depedendent default value has changed")
	}
	if bc.CompanyIdentification != "121042882" {
		t.Error("CompanyIdentification depedendent default value has changed")
	}
	if bc.ODFIIdentification != "12104288" {
		t.Error("ODFIIdentification depedendent default value has changed")
	}
}

// TestMockBatchControl test mock batch control
func TestMockBatchControl(t *testing.T) {
	testMockBatchControl(t)
}

// BenchmarkMockBatchControl benchmarks mock batch control
func BenchmarkMockBatchControl(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testMockBatchControl(b)
	}
}

// TestParseBatchControl parses a known Batch ControlRecord string.
func testParseBatchControl(t testing.TB) {
	var line = "82250000010005320001000000010500000000000000origid                             076401250000001"
	r := NewReader(strings.NewReader(line))
	r.line = line
	bh := BatchHeader{BatchNumber: 1,
		ServiceClassCode:      225,
		CompanyIdentification: "origid",
		ODFIIdentification:    "7640125"}
	r.addCurrentBatch(NewBatchPPD(&bh))

	r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 27, Amount: 10500, RDFIIdentification: "5320001", TraceNumber: 76401255655291})
	if err := r.parseBatchControl(); err != nil {
		t.Errorf("%T: %s", err, err)
	}
	record := r.currentBatch.GetControl()

	if record.recordType != "8" {
		t.Errorf("RecordType Expected '8' got: %v", record.recordType)
	}
	if record.ServiceClassCode != 225 {
		t.Errorf("ServiceClassCode Expected '225' got: %v", record.ServiceClassCode)
	}
	if record.EntryAddendaCountField() != "000001" {
		t.Errorf("EntryAddendaCount Expected '000001' got: %v", record.EntryAddendaCountField())
	}
	if record.EntryHashField() != "0005320001" {
		t.Errorf("EntryHash Expected '0005320001' got: %v", record.EntryHashField())
	}
	if record.TotalDebitEntryDollarAmountField() != "000000010500" {
		t.Errorf("TotalDebitEntryDollarAmount Expected '000000010500' got: %v", record.TotalDebitEntryDollarAmountField())
	}
	if record.TotalCreditEntryDollarAmountField() != "000000000000" {
		t.Errorf("TotalCreditEntryDollarAmount Expected '000000000000' got: %v", record.TotalCreditEntryDollarAmountField())
	}
	if record.CompanyIdentificationField() != "origid    " {
		t.Errorf("CompanyIdentification Expected 'origid    ' got: %v", record.CompanyIdentificationField())
	}
	if record.MessageAuthenticationCodeField() != "                   " {
		t.Errorf("MessageAuthenticationCode Expected '                   ' got: %v", record.MessageAuthenticationCodeField())
	}
	if record.reserved != "      " {
		t.Errorf("Reserved Expected '      ' got: %v", record.reserved)
	}
	if record.ODFIIdentificationField() != "07640125" {
		t.Errorf("OdfiIdentification Expected '07640125' got: %v", record.ODFIIdentificationField())
	}
	if record.BatchNumberField() != "0000001" {
		t.Errorf("BatchNumber Expected '0000001' got: %v", record.BatchNumberField())
	}
}

// TestParseBatchControl tests parsing a known Batch ControlRecord string.
func TestParseBatchControl(t *testing.T) {
	testParseBatchControl(t)
}

// BenchmarkParseBatchControl benchmarks parsing a known Batch ControlRecord string.
func BenchmarkParseBatchControl(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testParseBatchControl(b)
	}
}

// TestBCString validates that a known parsed file can be return to a string of the same value
func testBCString(t testing.TB) {
	var line = "82250000010005320001000000010500000000000000origid                             076401250000001"
	r := NewReader(strings.NewReader(line))
	r.line = line
	bh := BatchHeader{BatchNumber: 1,
		ServiceClassCode:      225,
		CompanyIdentification: "origid",
		ODFIIdentification:    "7640125"}
	r.addCurrentBatch(NewBatchPPD(&bh))

	r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 27, Amount: 10500, RDFIIdentification: "5320001", TraceNumber: 76401255655291})
	if err := r.parseBatchControl(); err != nil {
		t.Errorf("%T: %s", err, err)
	}
	record := r.currentBatch.GetControl()

	if record.String() != line {
		t.Errorf("Strings do not match")
	}
}

// TestBCString tests validating that a known parsed file can be return to a string of the same value
func TestBCString(t *testing.T) {
	testBCString(t)
}

// BenchmarkBCString benchmarks validating that a known parsed file can be return to a string of the same value
func BenchmarkBCString(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBCString(b)
	}
}

// TestValidateBCRecordType ensure error if recordType is not 8
func testValidateBCRecordType(t testing.TB) {
	bc := mockBatchControl()
	bc.recordType = "2"
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "recordType" {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

// TestValidateBCRecordType tests ensuring an error if recordType is not 8
func TestValidateBCRecordType(t *testing.T) {
	testValidateBCRecordType(t)
}

// BenchmarkValidateBCRecordType benchmarks ensuring an error if recordType is not 8
func BenchmarkValidateBCRecordType(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testValidateBCRecordType(b)
	}
}

// testBCisServiceClassErr verifies service class code
func testBCisServiceClassErr(t testing.TB) {
	bc := mockBatchControl()
	bc.ServiceClassCode = 123
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "ServiceClassCode" {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

// TestBCisServiceClassErr tests verifying service class code
func TestBCisServiceClassErr(t *testing.T) {
	testBCisServiceClassErr(t)
}

// BenchmarkBCisServiceClassErr benchmarks verifying service class code
func BenchmarkBCisServiceClassErr(b *testing.B) {
	for i := 0; i < b.N; i++ {
		testBCisServiceClassErr(b)
	}
}

// testBCBatchNumber verifies batch number
func testBCBatchNumber(t testing.TB) {
	bc := mockBatchControl()
	bc.BatchNumber = 0
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "BatchNumber" {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

// TestBCBatchNumber tests verifying batch number
func TestBCBatchNumber(t *testing.T) {
	testBCBatchNumber(t)
}

// BenchmarkBCBatchNumber benchmarks verifying batch number
func BenchmarkBCBatchNumber(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBCBatchNumber(b)
	}
}

// testBCCompanyIdentificationAlphaNumeric verifies Company Identification is AlphaNumeric
func testBCCompanyIdentificationAlphaNumeric(t testing.TB) {
	bc := mockBatchControl()
	bc.CompanyIdentification = "®"
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "CompanyIdentification" {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

// TestBCCompanyIdentificationAlphaNumeric tests verifying Company Identification is AlphaNumeric
func TestBCCompanyIdentificationAlphaNumeric(t *testing.T) {
	testBCCompanyIdentificationAlphaNumeric(t)
}

// BenchmarkBCCompanyIdentificationAlphaNumeric benchmarks verifying Company Identification is AlphaNumeric
func BenchmarkBCCompanyIdentificationAlphaNumeric(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBCCompanyIdentificationAlphaNumeric(b)
	}
}

// testBCMessageAuthenticationCodeAlphaNumeric verifies AuthenticationCode is AlphaNumeric
func testBCMessageAuthenticationCodeAlphaNumeric(t testing.TB) {
	bc := mockBatchControl()
	bc.MessageAuthenticationCode = "®"
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "MessageAuthenticationCode" {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

// TestBCMessageAuthenticationCodeAlphaNumeric tests verifying AuthenticationCode is AlphaNumeric
func TestBCMessageAuthenticationCodeAlphaNumeric(t *testing.T) {
	testBCMessageAuthenticationCodeAlphaNumeric(t)
}

// TestBCMessageAuthenticationCodeAlphaNumeric benchmarks verifying AuthenticationCode is AlphaNumeric
func BenchmarkBCMessageAuthenticationCodeAlphaNumeric(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBCMessageAuthenticationCodeAlphaNumeric(b)
	}
}

// testBCFieldInclusionRecordType verifies Record Type is included
func testBCFieldInclusionRecordType(t testing.TB) {
	bc := mockBatchControl()
	bc.recordType = ""
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.Msg != msgFieldInclusion {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

// TestBCFieldInclusionRecordType tests verifying Record Type is included
func TestBCFieldInclusionRecordType(t *testing.T) {
	testBCFieldInclusionRecordType(t)
}

// BenchmarkBCFieldInclusionRecordType benchmarks verifying Record Type is included
func BenchmarkBCFieldInclusionRecordType(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBCFieldInclusionRecordType(b)
	}
}

// testBCFieldInclusionServiceClassCode verifies Service Class Code is included
func testBCFieldInclusionServiceClassCode(t testing.TB) {
	bc := mockBatchControl()
	bc.ServiceClassCode = 0
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.Msg != msgFieldInclusion {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

// TestBCFieldInclusionServiceClassCode tests verifying Service Class Code is included
func TestBCFieldInclusionServiceClassCode(t *testing.T) {
	testBCFieldInclusionServiceClassCode(t)
}

// BenchmarkBCFieldInclusionServiceClassCod benchmarks verifying Service Class Code is included
func BenchmarkBCFieldInclusionServiceClassCode(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBCFieldInclusionServiceClassCode(b)
	}
}

// testBCFieldInclusionODFIIdentification verifies batch control ODFIIdentification
func testBCFieldInclusionODFIIdentification(t testing.TB) {
	bc := mockBatchControl()
	bc.ODFIIdentification = "000000000"
	if err := bc.Validate(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.Msg != msgFieldInclusion {
				t.Errorf("%T: %s", err, err)
			}
		}
	}
}

// TestBCFieldInclusionODFIIdentification tests verifying batch control ODFIIdentification
func TestBCFieldInclusionODFIIdentification(t *testing.T) {
	testBCFieldInclusionODFIIdentification(t)
}

// BenchmarkBCFieldInclusionODFIIdentification benchmarks verifying batch control ODFIIdentification
func BenchmarkBCFieldInclusionODFIIdentification(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBCFieldInclusionODFIIdentification(b)
	}
}

// testBatchControlLength verifies batch control length
func testBatchControlLength(t testing.TB) {
	bc := NewBatchControl()
	recordLength := len(bc.String())
	if recordLength != 94 {
		t.Errorf("Instantiated length of Batch Control string is not 94 but %v", recordLength)
	}
}

// TestBatchControlLength tests verifying batch control length
func TestBatchControlLength(t *testing.T) {
	testBatchControlLength(t)
}

// BenchmarkBatchControlLength benchmarks verifying batch control length
func BenchmarkBatchControlLength(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchControlLength(b)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tableconvertor

import (
	"context"
	"fmt"
	"reflect"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/diff"
	"k8s.io/client-go/util/jsonpath"
)

func Test_cellForJSONValue(t *testing.T) {
	tests := []struct {
		headerType string
		value      interface{}
		want       interface{}
	}{
		{"integer", int64(42), int64(42)},
		{"integer", float64(3.14), int64(3)},
		{"integer", true, nil},
		{"integer", "foo", nil},

		{"number", int64(42), float64(42)},
		{"number", float64(3.14), float64(3.14)},
		{"number", true, nil},
		{"number", "foo", nil},

		{"boolean", int64(42), nil},
		{"boolean", float64(3.14), nil},
		{"boolean", true, true},
		{"boolean", "foo", nil},

		{"string", int64(42), nil},
		{"string", float64(3.14), nil},
		{"string", true, nil},
		{"string", "foo", "foo"},

		{"date", int64(42), nil},
		{"date", float64(3.14), nil},
		{"date", true, nil},
		{"date", time.Now().Add(-time.Hour*12 - 30*time.Minute).UTC().Format(time.RFC3339), "12h"},
		{"date", time.Now().Add(+time.Hour*12 + 30*time.Minute).UTC().Format(time.RFC3339), "<invalid>"},
		{"date", "", "<unknown>"},

		{"unknown", "foo", nil},
	}
	for _, tt := range tests {
		t.Run(fmt.Sprintf("%#v of type %s", tt.value, tt.headerType), func(t *testing.T) {
			if got := cellForJSONValue(tt.headerType, tt.value); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("cellForJSONValue() = %#v, want %#v", got, tt.want)
			}
		})
	}
}

func Test_convertor_ConvertToTable(t *testing.T) {
	type fields struct {
		headers           []metav1beta1.TableColumnDefinition
		additionalColumns []*jsonpath.JSONPath
	}
	type args struct {
		ctx          context.Context
		obj          runtime.Object
		tableOptions runtime.Object
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    *metav1beta1.Table
		wantErr bool
	}{
		{
			name: "Return table for object",
			fields: fields{
				headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
			},
			args: args{
				obj: &metav1beta1.PartialObjectMetadata{
					ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
				},
				tableOptions: nil,
			},
			want: &metav1beta1.Table{
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
				Rows: []metav1beta1.TableRow{
					{
						Cells: []interface{}{"blah"},
						Object: runtime.RawExtension{
							Object: &metav1beta1.PartialObjectMetadata{
								ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
							},
						},
					},
				},
			},
		},
		{
			name: "Return table for list",
			fields: fields{
				headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
			},
			args: args{
				obj: &metav1beta1.PartialObjectMetadataList{
					Items: []*metav1beta1.PartialObjectMetadata{
						{ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))}},
						{ObjectMeta: metav1.ObjectMeta{Name: "blah-2", CreationTimestamp: metav1.NewTime(time.Unix(2, 0))}},
					},
				},
				tableOptions: nil,
			},
			want: &metav1beta1.Table{
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
				Rows: []metav1beta1.TableRow{
					{
						Cells: []interface{}{"blah"},
						Object: runtime.RawExtension{
							Object: &metav1beta1.PartialObjectMetadata{
								ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
							},
						},
					},
					{
						Cells: []interface{}{"blah-2"},
						Object: runtime.RawExtension{
							Object: &metav1beta1.PartialObjectMetadata{
								ObjectMeta: metav1.ObjectMeta{Name: "blah-2", CreationTimestamp: metav1.NewTime(time.Unix(2, 0))},
							},
						},
					},
				},
			},
		},
		{
			name: "Accept TableOptions",
			fields: fields{
				headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
			},
			args: args{
				obj: &metav1beta1.PartialObjectMetadata{
					ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
				},
				tableOptions: &metav1beta1.TableOptions{},
			},
			want: &metav1beta1.Table{
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
				Rows: []metav1beta1.TableRow{
					{
						Cells: []interface{}{"blah"},
						Object: runtime.RawExtension{
							Object: &metav1beta1.PartialObjectMetadata{
								ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
							},
						},
					},
				},
			},
		},
		{
			name: "Omit headers from TableOptions",
			fields: fields{
				headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
			},
			args: args{
				obj: &metav1beta1.PartialObjectMetadata{
					ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
				},
				tableOptions: &metav1beta1.TableOptions{NoHeaders: true},
			},
			want: &metav1beta1.Table{
				Rows: []metav1beta1.TableRow{
					{
						Cells: []interface{}{"blah"},
						Object: runtime.RawExtension{
							Object: &metav1beta1.PartialObjectMetadata{
								ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
							},
						},
					},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := &convertor{
				headers:           tt.fields.headers,
				additionalColumns: tt.fields.additionalColumns,
			}
			got, err := c.ConvertToTable(tt.args.ctx, tt.args.obj, tt.args.tableOptions)
			if (err != nil) != tt.wantErr {
				t.Errorf("convertor.ConvertToTable() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("convertor.ConvertToTable() = %s", diff.ObjectReflectDiff(tt.want, got))
			}
		})
	}
}
<commit_msg>PartialObjectMetadataList should nest values, not pointers for Items<commit_after>/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tableconvertor

import (
	"context"
	"fmt"
	"reflect"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/diff"
	"k8s.io/client-go/util/jsonpath"
)

func Test_cellForJSONValue(t *testing.T) {
	tests := []struct {
		headerType string
		value      interface{}
		want       interface{}
	}{
		{"integer", int64(42), int64(42)},
		{"integer", float64(3.14), int64(3)},
		{"integer", true, nil},
		{"integer", "foo", nil},

		{"number", int64(42), float64(42)},
		{"number", float64(3.14), float64(3.14)},
		{"number", true, nil},
		{"number", "foo", nil},

		{"boolean", int64(42), nil},
		{"boolean", float64(3.14), nil},
		{"boolean", true, true},
		{"boolean", "foo", nil},

		{"string", int64(42), nil},
		{"string", float64(3.14), nil},
		{"string", true, nil},
		{"string", "foo", "foo"},

		{"date", int64(42), nil},
		{"date", float64(3.14), nil},
		{"date", true, nil},
		{"date", time.Now().Add(-time.Hour*12 - 30*time.Minute).UTC().Format(time.RFC3339), "12h"},
		{"date", time.Now().Add(+time.Hour*12 + 30*time.Minute).UTC().Format(time.RFC3339), "<invalid>"},
		{"date", "", "<unknown>"},

		{"unknown", "foo", nil},
	}
	for _, tt := range tests {
		t.Run(fmt.Sprintf("%#v of type %s", tt.value, tt.headerType), func(t *testing.T) {
			if got := cellForJSONValue(tt.headerType, tt.value); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("cellForJSONValue() = %#v, want %#v", got, tt.want)
			}
		})
	}
}

func Test_convertor_ConvertToTable(t *testing.T) {
	type fields struct {
		headers           []metav1beta1.TableColumnDefinition
		additionalColumns []*jsonpath.JSONPath
	}
	type args struct {
		ctx          context.Context
		obj          runtime.Object
		tableOptions runtime.Object
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    *metav1beta1.Table
		wantErr bool
	}{
		{
			name: "Return table for object",
			fields: fields{
				headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
			},
			args: args{
				obj: &metav1beta1.PartialObjectMetadata{
					ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
				},
				tableOptions: nil,
			},
			want: &metav1beta1.Table{
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
				Rows: []metav1beta1.TableRow{
					{
						Cells: []interface{}{"blah"},
						Object: runtime.RawExtension{
							Object: &metav1beta1.PartialObjectMetadata{
								ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
							},
						},
					},
				},
			},
		},
		{
			name: "Return table for list",
			fields: fields{
				headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
			},
			args: args{
				obj: &metav1beta1.PartialObjectMetadataList{
					Items: []metav1beta1.PartialObjectMetadata{
						{ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))}},
						{ObjectMeta: metav1.ObjectMeta{Name: "blah-2", CreationTimestamp: metav1.NewTime(time.Unix(2, 0))}},
					},
				},
				tableOptions: nil,
			},
			want: &metav1beta1.Table{
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
				Rows: []metav1beta1.TableRow{
					{
						Cells: []interface{}{"blah"},
						Object: runtime.RawExtension{
							Object: &metav1beta1.PartialObjectMetadata{
								ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
							},
						},
					},
					{
						Cells: []interface{}{"blah-2"},
						Object: runtime.RawExtension{
							Object: &metav1beta1.PartialObjectMetadata{
								ObjectMeta: metav1.ObjectMeta{Name: "blah-2", CreationTimestamp: metav1.NewTime(time.Unix(2, 0))},
							},
						},
					},
				},
			},
		},
		{
			name: "Accept TableOptions",
			fields: fields{
				headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
			},
			args: args{
				obj: &metav1beta1.PartialObjectMetadata{
					ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
				},
				tableOptions: &metav1beta1.TableOptions{},
			},
			want: &metav1beta1.Table{
				ColumnDefinitions: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
				Rows: []metav1beta1.TableRow{
					{
						Cells: []interface{}{"blah"},
						Object: runtime.RawExtension{
							Object: &metav1beta1.PartialObjectMetadata{
								ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
							},
						},
					},
				},
			},
		},
		{
			name: "Omit headers from TableOptions",
			fields: fields{
				headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
			},
			args: args{
				obj: &metav1beta1.PartialObjectMetadata{
					ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
				},
				tableOptions: &metav1beta1.TableOptions{NoHeaders: true},
			},
			want: &metav1beta1.Table{
				Rows: []metav1beta1.TableRow{
					{
						Cells: []interface{}{"blah"},
						Object: runtime.RawExtension{
							Object: &metav1beta1.PartialObjectMetadata{
								ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
							},
						},
					},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := &convertor{
				headers:           tt.fields.headers,
				additionalColumns: tt.fields.additionalColumns,
			}
			got, err := c.ConvertToTable(tt.args.ctx, tt.args.obj, tt.args.tableOptions)
			if (err != nil) != tt.wantErr {
				t.Errorf("convertor.ConvertToTable() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("convertor.ConvertToTable() = %s", diff.ObjectReflectDiff(tt.want, got))
			}
		})
	}
}
<|endoftext|>


<commit_before>package common

import (
	"archive/zip"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	log "github.com/Sirupsen/logrus"
	"gitlab.ricebook.net/platform/core/types"
	git "gopkg.in/libgit2/git2go.v25"
)

// GitScm is gitlab or github source code manager
type GitScm struct {
	http.Client
	Config      types.GitConfig
	AuthHeaders map[string]string
}

func certificateCheckCallback(cert *git.Certificate, valid bool, hostname string) git.ErrorCode {
	return git.ErrorCode(0)
}

func gitcheck(repository, pubkey, prikey string) error {
	if !strings.HasPrefix(repository, "git@") {
		return fmt.Errorf("Only support ssh protocol(%q), use git@... ", repository)
	}
	if _, err := os.Stat(pubkey); os.IsNotExist(err) {
		return fmt.Errorf("Public Key not found(%q)", pubkey)
	}
	if _, err := os.Stat(prikey); os.IsNotExist(err) {
		return fmt.Errorf("Private Key not found(%q)", prikey)
	}

	return nil
}

// SourceCode clone code from repository into path, by revision
func (g *GitScm) SourceCode(repository, path, revision string) error {
	pubkey := g.Config.PublicKey
	prikey := g.Config.PrivateKey

	if err := gitcheck(repository, pubkey, prikey); err != nil {
		return err
	}

	credentialsCallback := func(url, username string, allowedTypes git.CredType) (git.ErrorCode, *git.Cred) {
		ret, cred := git.NewCredSshKey("git", pubkey, prikey, "")
		return git.ErrorCode(ret), &cred
	}

	cloneOpts := &git.CloneOptions{
		FetchOptions: &git.FetchOptions{
			RemoteCallbacks: git.RemoteCallbacks{
				CredentialsCallback:      credentialsCallback,
				CertificateCheckCallback: certificateCheckCallback,
			},
		},
	}

	repo, err := git.Clone(repository, path, cloneOpts)
	if err != nil {
		return err
	}

	if err := repo.CheckoutHead(nil); err != nil {
		return err
	}

	object, err := repo.RevparseSingle(revision)
	if err != nil {
		return err
	}
	defer object.Free()
	commit, err := object.AsCommit()

	return repo.ResetToCommit(commit, git.ResetHard, &git.CheckoutOpts{Strategy: git.CheckoutSafe})
}

// Artifact download the artifact to the path, then unzip it
func (g *GitScm) Artifact(artifact, path string) error {
	req, err := http.NewRequest("GET", artifact, nil)
	if err != nil {
		return err
	}

	for k, v := range g.AuthHeaders {
		req.Header.Add(k, v)
	}

	log.Debugf("Downloading artifacts from %q", artifact)
	resp, err := g.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return fmt.Errorf("Download artifact error %q", artifact)
	}
	log.Debugf("Download artifacts from %q finished", artifact)

	// extract files from zipfile
	log.Debugf("Extracting files from %q", artifact)
	if err := UnzipFile(resp.Body, path); err != nil {
		return err
	}
	log.Debugf("Extraction from %q done", artifact)

	return nil
}

// UnzipFile unzip a file(from resp.Body) to the spec path
func UnzipFile(body io.ReadCloser, path string) error {
	content, err := ioutil.ReadAll(body)
	if err != nil {
		return err
	}

	reader, err := zip.NewReader(bytes.NewReader(content), int64(len(content)))
	if err != nil {
		return err
	}

	// extract files from zipfile
	for _, f := range reader.File {
		zipped, err := f.Open()
		if err != nil {
			return err
		}

		defer zipped.Close()

		p := filepath.Join(path, f.Name)

		if f.FileInfo().IsDir() {
			os.MkdirAll(p, f.Mode())
			continue
		}

		writer, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE, f.Mode())
		if err != nil {
			return err
		}

		defer writer.Close()
		if _, err = io.Copy(writer, zipped); err != nil {
			return err
		}
	}
	return nil
}

// ZipFiles compresses one or many files into a single zip archive file
func ZipFiles(filename string, files []string) error {

	newfile, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer newfile.Close()

	zipWriter := zip.NewWriter(newfile)
	defer zipWriter.Close()

	// Add files to zip
	for _, file := range files {

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

		// Get the file information
		info, err := zipfile.Stat()
		if err != nil {
			return err
		}

		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}

		// Change to deflate to gain better compression
		// see http://golang.org/pkg/archive/zip/#pkg-constants
		header.Method = zip.Deflate

		writer, err := zipWriter.CreateHeader(header)
		if err != nil {
			return err
		}
		_, err = io.Copy(writer, zipfile)
		if err != nil {
			return err
		}
	}
	return nil
}

// Security remove the .git folder
func (g *GitScm) Security(path string) error {
	return os.RemoveAll(filepath.Join(path, ".git"))
}
<commit_msg>add some more debug info when downloading artifacts<commit_after>package common

import (
	"archive/zip"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	log "github.com/Sirupsen/logrus"
	"gitlab.ricebook.net/platform/core/types"
	git "gopkg.in/libgit2/git2go.v25"
)

// GitScm is gitlab or github source code manager
type GitScm struct {
	http.Client
	Config      types.GitConfig
	AuthHeaders map[string]string
}

func certificateCheckCallback(cert *git.Certificate, valid bool, hostname string) git.ErrorCode {
	return git.ErrorCode(0)
}

func gitcheck(repository, pubkey, prikey string) error {
	if !strings.HasPrefix(repository, "git@") {
		return fmt.Errorf("Only support ssh protocol(%q), use git@... ", repository)
	}
	if _, err := os.Stat(pubkey); os.IsNotExist(err) {
		return fmt.Errorf("Public Key not found(%q)", pubkey)
	}
	if _, err := os.Stat(prikey); os.IsNotExist(err) {
		return fmt.Errorf("Private Key not found(%q)", prikey)
	}

	return nil
}

// SourceCode clone code from repository into path, by revision
func (g *GitScm) SourceCode(repository, path, revision string) error {
	pubkey := g.Config.PublicKey
	prikey := g.Config.PrivateKey

	if err := gitcheck(repository, pubkey, prikey); err != nil {
		return err
	}

	credentialsCallback := func(url, username string, allowedTypes git.CredType) (git.ErrorCode, *git.Cred) {
		ret, cred := git.NewCredSshKey("git", pubkey, prikey, "")
		return git.ErrorCode(ret), &cred
	}

	cloneOpts := &git.CloneOptions{
		FetchOptions: &git.FetchOptions{
			RemoteCallbacks: git.RemoteCallbacks{
				CredentialsCallback:      credentialsCallback,
				CertificateCheckCallback: certificateCheckCallback,
			},
		},
	}

	repo, err := git.Clone(repository, path, cloneOpts)
	if err != nil {
		return err
	}

	if err := repo.CheckoutHead(nil); err != nil {
		return err
	}

	object, err := repo.RevparseSingle(revision)
	if err != nil {
		return err
	}
	defer object.Free()
	commit, err := object.AsCommit()

	return repo.ResetToCommit(commit, git.ResetHard, &git.CheckoutOpts{Strategy: git.CheckoutSafe})
}

// Artifact download the artifact to the path, then unzip it
func (g *GitScm) Artifact(artifact, path string) error {
	req, err := http.NewRequest("GET", artifact, nil)
	if err != nil {
		return err
	}

	for k, v := range g.AuthHeaders {
		req.Header.Add(k, v)
	}

	log.Debugf("Downloading artifacts from %q", artifact)
	resp, err := g.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return fmt.Errorf("Download artifact error %q, code %d", artifact, resp.StatusCode)
	}
	log.Debugf("Download artifacts from %q finished", artifact)

	// extract files from zipfile
	log.Debugf("Extracting files from %q", artifact)
	if err := UnzipFile(resp.Body, path); err != nil {
		return err
	}
	log.Debugf("Extraction from %q done", artifact)

	return nil
}

// UnzipFile unzip a file(from resp.Body) to the spec path
func UnzipFile(body io.ReadCloser, path string) error {
	content, err := ioutil.ReadAll(body)
	if err != nil {
		return err
	}

	reader, err := zip.NewReader(bytes.NewReader(content), int64(len(content)))
	if err != nil {
		return err
	}

	// extract files from zipfile
	for _, f := range reader.File {
		zipped, err := f.Open()
		if err != nil {
			return err
		}

		defer zipped.Close()

		p := filepath.Join(path, f.Name)

		if f.FileInfo().IsDir() {
			os.MkdirAll(p, f.Mode())
			continue
		}

		writer, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE, f.Mode())
		if err != nil {
			return err
		}

		defer writer.Close()
		if _, err = io.Copy(writer, zipped); err != nil {
			return err
		}
	}
	return nil
}

// ZipFiles compresses one or many files into a single zip archive file
func ZipFiles(filename string, files []string) error {

	newfile, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer newfile.Close()

	zipWriter := zip.NewWriter(newfile)
	defer zipWriter.Close()

	// Add files to zip
	for _, file := range files {

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

		// Get the file information
		info, err := zipfile.Stat()
		if err != nil {
			return err
		}

		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}

		// Change to deflate to gain better compression
		// see http://golang.org/pkg/archive/zip/#pkg-constants
		header.Method = zip.Deflate

		writer, err := zipWriter.CreateHeader(header)
		if err != nil {
			return err
		}
		_, err = io.Copy(writer, zipfile)
		if err != nil {
			return err
		}
	}
	return nil
}

// Security remove the .git folder
func (g *GitScm) Security(path string) error {
	return os.RemoveAll(filepath.Join(path, ".git"))
}
<|endoftext|>


<commit_before>// Description: utils/strings
// Author: ZHU HAIHUA
// Since: 2016-04-08 19:45
package util

import (
	"encoding/json"
	"strconv"
	"fmt"
	. "reflect"
)

type Style int

const (
	StyleShort Style = iota
	StyleLong
)

// ToJson return the json format of the obj
// when error occur it will return empty.
// Notice: unexported field will not be marshaled
func ToJson(obj interface{}) string {
	result, err := json.Marshal(obj)
	if err != nil {
		return fmt.Sprintf("<no value with error: %v>", err)
	}
	return string(result)
}

// ReflectToString return the string format of the given argument,
// the long style may be a very long format like following:
//
//      Type{name=value}
//
// otherwise the short format will only print the value but not type
// and name information
func ReflectToString(obj interface{}, style Style) string {
	var result string
	switch style {
	case StyleShort:
		result = fmt.Sprintf("%v", obj)
	case StyleLong:
		result = valueToString(ValueOf(obj))
	}
	return result
}

// valueToString recursively print all the value
func valueToString(val Value) string {

	var str string
	if !val.IsValid() {
		return "<zero Value>"
	}
	typ := val.Type()
	switch val.Kind() {
	case Int, Int8, Int16, Int32, Int64:
		return strconv.FormatInt(val.Int(), 10)
	case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
		return strconv.FormatUint(val.Uint(), 10)
	case Float32, Float64:
		return strconv.FormatFloat(val.Float(), 'g', -1, 64)
	case Complex64, Complex128:
		c := val.Complex()
		return strconv.FormatFloat(real(c), 'g', -1, 64) + "+" + strconv.FormatFloat(imag(c), 'g', -1, 64) + "i"
	case String:
		return val.String()
	case Bool:
		if val.Bool() {
			return "true"
		} else {
			return "false"
		}
	case Ptr:
		v := val
		str = typ.String() + "("
		if v.IsNil() {
			str += "0"
		} else {
			str += "&" + valueToString(v.Elem())
		}
		str += ")"
		return str
	case Array, Slice:
		v := val
		str += typ.String()
		str += "{"
		for i := 0; i < v.Len(); i++ {
			if i > 0 {
				str += ", "
			}
			str += valueToString(v.Index(i))
		}
		str += "}"
		return str
	case Map:
		t := typ
		str = t.String()
		str += "{"
		//str += "<can't iterate on maps>"
		keys := val.MapKeys()
		for i, _ := range keys {
			if i > 0 {
				str += ","
			}
			str += valueToString(keys[i])
			str += "="
			str += valueToString(val.MapIndex(keys[i]))
		}
		str += "}"
		return str
	case Chan:
		str = typ.String()
		return str
	case Struct:
		t := typ
		v := val
		str += t.String()
		str += "{"
		for i, n := 0, v.NumField(); i < n; i++ {
			if i > 0 {
				str += ", "
			}
			str += val.Type().Field(i).Name
			str += "="
			str += valueToString(v.Field(i))
		}
		str += "}"
		return str
	case Interface:
		return typ.String() + "(" + valueToString(val.Elem()) + ")"
	case Func:
		v := val
		return typ.String() + "(" + strconv.FormatUint(uint64(v.Pointer()), 10) + ")"
	default:
		panic("valueToString: can't print type " + typ.String())
	}
}<commit_msg>add comments<commit_after>// Description: utils/strings.go
// Author: ZHU HAIHUA
// Since: 2016-04-08 19:45
package util

import (
	"encoding/json"
	"strconv"
	"fmt"
	. "reflect"
)

type Style int

const (
	StyleShort Style = iota
	StyleLong
)

// ToJson return the json format of the obj
// when error occur it will return empty.
// Notice: unexported field will not be marshaled
func ToJson(obj interface{}) string {
	result, err := json.Marshal(obj)
	if err != nil {
		return fmt.Sprintf("<no value with error: %v>", err)
	}
	return string(result)
}

// ReflectToString return the string format of the given argument,
// the long style may be a very long format like following:
//
//      Type{name=value}
//
// otherwise the short format will only print the value but not type
// and name information.
//
// since recursive call, this method would be pretty slow, so if you
// use it to print log, may be you need to check if the log level is
// enabled first
func ReflectToString(obj interface{}, style Style) string {
	var result string
	switch style {
	case StyleShort:
		result = fmt.Sprintf("%v", obj)
	case StyleLong:
		result = valueToString(ValueOf(obj))
	}
	return result
}

// valueToString recursively print all the value
func valueToString(val Value) string {

	var str string
	if !val.IsValid() {
		return "<zero Value>"
	}
	typ := val.Type()
	switch val.Kind() {
	case Int, Int8, Int16, Int32, Int64:
		return strconv.FormatInt(val.Int(), 10)
	case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
		return strconv.FormatUint(val.Uint(), 10)
	case Float32, Float64:
		return strconv.FormatFloat(val.Float(), 'g', -1, 64)
	case Complex64, Complex128:
		c := val.Complex()
		return strconv.FormatFloat(real(c), 'g', -1, 64) + "+" + strconv.FormatFloat(imag(c), 'g', -1, 64) + "i"
	case String:
		return val.String()
	case Bool:
		if val.Bool() {
			return "true"
		} else {
			return "false"
		}
	case Ptr:
		v := val
		str = typ.String() + "("
		if v.IsNil() {
			str += "0"
		} else {
			str += "&" + valueToString(v.Elem())
		}
		str += ")"
		return str
	case Array, Slice:
		v := val
		str += typ.String()
		str += "{"
		for i := 0; i < v.Len(); i++ {
			if i > 0 {
				str += ", "
			}
			str += valueToString(v.Index(i))
		}
		str += "}"
		return str
	case Map:
		t := typ
		str = t.String()
		str += "{"
		//str += "<can't iterate on maps>"
		keys := val.MapKeys()
		for i, _ := range keys {
			if i > 0 {
				str += ","
			}
			str += valueToString(keys[i])
			str += "="
			str += valueToString(val.MapIndex(keys[i]))
		}
		str += "}"
		return str
	case Chan:
		str = typ.String()
		return str
	case Struct:
		t := typ
		v := val
		str += t.String()
		str += "{"
		for i, n := 0, v.NumField(); i < n; i++ {
			if i > 0 {
				str += ", "
			}
			str += val.Type().Field(i).Name
			str += "="
			str += valueToString(v.Field(i))
		}
		str += "}"
		return str
	case Interface:
		return typ.String() + "(" + valueToString(val.Elem()) + ")"
	case Func:
		v := val
		return typ.String() + "(" + strconv.FormatUint(uint64(v.Pointer()), 10) + ")"
	default:
		panic("valueToString: can't print type " + typ.String())
	}
}<|endoftext|>


<commit_before>package totp

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/binary"
	"fmt"
	"hash"
	"time"
)

type Token struct {
	key      []byte
	epoch    time.Time
	interval time.Duration
	hash     func() hash.Hash
}

func New(key []byte) Token {
	token := Token{
		key:      key,
		epoch:    time.Unix(0, 0).UTC(),
		interval: 30 * time.Second,
		hash:     sha256.New,
	}
	return token
}

func NewHashToken(key []byte, h func() hash.Hash) Token {
	token := New(key)
	token.hash = h
	return token
}

func (token Token) Generate(t time.Time, length int) string {
	c := (t.Unix() - token.epoch.Unix()) / int64(token.interval.Seconds())
	h := hmac.New(token.hash, token.key)

	var b bytes.Buffer
	binary.Write(&b, binary.BigEndian, c)
	h.Write(b.Bytes())

	v := h.Sum(nil)
	o := v[len(v)-1] & 0xf
	val := (int32(v[o]&0x7f)<<24 |
		int32(v[o+1])<<16 |
		int32(v[o+2])<<8 |
		int32(v[o+3])) % 1000000000

	return fmt.Sprintf("%010d", val)[10-length : 10]
}

func (t Token) String() string {
	return t.Generate(time.Now().UTC(), 6)
}
<commit_msg>Version bump<commit_after>package totp

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/binary"
	"fmt"
	"hash"
	"time"
)

const (
	VERSION = "1.0.0"
)

type Token struct {
	key      []byte
	epoch    time.Time
	interval time.Duration
	hash     func() hash.Hash
}

func New(key []byte) Token {
	token := Token{
		key:      key,
		epoch:    time.Unix(0, 0).UTC(),
		interval: 30 * time.Second,
		hash:     sha256.New,
	}
	return token
}

func NewHashToken(key []byte, h func() hash.Hash) Token {
	token := New(key)
	token.hash = h
	return token
}

func (token Token) Generate(t time.Time, length int) string {
	c := (t.Unix() - token.epoch.Unix()) / int64(token.interval.Seconds())
	h := hmac.New(token.hash, token.key)

	var b bytes.Buffer
	binary.Write(&b, binary.BigEndian, c)
	h.Write(b.Bytes())

	v := h.Sum(nil)
	o := v[len(v)-1] & 0xf
	val := (int32(v[o]&0x7f)<<24 |
		int32(v[o+1])<<16 |
		int32(v[o+2])<<8 |
		int32(v[o+3])) % 1000000000

	return fmt.Sprintf("%010d", val)[10-length : 10]
}

func (t Token) String() string {
	return t.Generate(time.Now().UTC(), 6)
}
<|endoftext|>


<commit_before>package weed_server

import (
	"bytes"
	"context"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/util/mem"
	"io"
	"mime"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/images"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	xhttp "github.com/chrislusf/seaweedfs/weed/s3api/http"
	"github.com/chrislusf/seaweedfs/weed/stats"
	"github.com/chrislusf/seaweedfs/weed/util"
)

// Validates the preconditions. Returns true if GET/HEAD operation should not proceed.
// Preconditions supported are:
//  If-Modified-Since
//  If-Unmodified-Since
//  If-Match
//  If-None-Match
func checkPreconditions(w http.ResponseWriter, r *http.Request, entry *filer.Entry) bool {

	etag := filer.ETagEntry(entry)
	/// When more than one conditional request header field is present in a
	/// request, the order in which the fields are evaluated becomes
	/// important.  In practice, the fields defined in this document are
	/// consistently implemented in a single, logical order, since "lost
	/// update" preconditions have more strict requirements than cache
	/// validation, a validated cache is more efficient than a partial
	/// response, and entity tags are presumed to be more accurate than date
	/// validators. https://tools.ietf.org/html/rfc7232#section-5
	if entry.Attr.Mtime.IsZero() {
		return false
	}
	w.Header().Set("Last-Modified", entry.Attr.Mtime.UTC().Format(http.TimeFormat))

	ifMatchETagHeader := r.Header.Get("If-Match")
	ifUnmodifiedSinceHeader := r.Header.Get("If-Unmodified-Since")
	if ifMatchETagHeader != "" {
		if util.CanonicalizeETag(etag) != util.CanonicalizeETag(ifMatchETagHeader) {
			w.WriteHeader(http.StatusPreconditionFailed)
			return true
		}
	} else if ifUnmodifiedSinceHeader != "" {
		if t, parseError := time.Parse(http.TimeFormat, ifUnmodifiedSinceHeader); parseError == nil {
			if t.Before(entry.Attr.Mtime) {
				w.WriteHeader(http.StatusPreconditionFailed)
				return true
			}
		}
	}

	ifNoneMatchETagHeader := r.Header.Get("If-None-Match")
	ifModifiedSinceHeader := r.Header.Get("If-Modified-Since")
	if ifNoneMatchETagHeader != "" {
		if util.CanonicalizeETag(etag) == util.CanonicalizeETag(ifNoneMatchETagHeader) {
			w.WriteHeader(http.StatusNotModified)
			return true
		}
	} else if ifModifiedSinceHeader != "" {
		if t, parseError := time.Parse(http.TimeFormat, ifModifiedSinceHeader); parseError == nil {
			if t.After(entry.Attr.Mtime) {
				w.WriteHeader(http.StatusNotModified)
				return true
			}
		}
	}

	return false
}

func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {

	path := r.URL.Path
	isForDirectory := strings.HasSuffix(path, "/")
	if isForDirectory && len(path) > 1 {
		path = path[:len(path)-1]
	}

	entry, err := fs.filer.FindEntry(context.Background(), util.FullPath(path))
	if err != nil {
		if path == "/" {
			fs.listDirectoryHandler(w, r)
			return
		}
		if err == filer_pb.ErrNotFound {
			glog.V(1).Infof("Not found %s: %v", path, err)
			stats.FilerRequestCounter.WithLabelValues(stats.ErrorReadNotFound).Inc()
			w.WriteHeader(http.StatusNotFound)
		} else {
			glog.Errorf("Internal %s: %v", path, err)
			stats.FilerRequestCounter.WithLabelValues(stats.ErrorReadInternal).Inc()
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}

	if entry.IsDirectory() {
		if fs.option.DisableDirListing {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		fs.listDirectoryHandler(w, r)
		return
	}

	if isForDirectory {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	query := r.URL.Query()
	if query.Get("metadata") == "true" {
		if query.Get("resolveManifest") == "true" {
			if entry.Chunks, _, err = filer.ResolveChunkManifest(
				fs.filer.MasterClient.GetLookupFileIdFunction(),
				entry.Chunks, 0, int64(entry.Size())); err != nil {
				err = fmt.Errorf("failed to resolve chunk manifest, err: %s", err.Error())
				writeJsonError(w, r, http.StatusInternalServerError, err)
			}
		}
		writeJsonQuiet(w, r, http.StatusOK, entry)
		return
	}

	etag := filer.ETagEntry(entry)
	if checkPreconditions(w, r, entry) {
		return
	}

	w.Header().Set("Accept-Ranges", "bytes")

	// mime type
	mimeType := entry.Attr.Mime
	if mimeType == "" {
		if ext := filepath.Ext(entry.Name()); ext != "" {
			mimeType = mime.TypeByExtension(ext)
		}
	}
	if mimeType != "" {
		w.Header().Set("Content-Type", mimeType)
	}

	// print out the header from extended properties
	for k, v := range entry.Extended {
		if !strings.HasPrefix(k, "xattr-") {
			// "xattr-" prefix is set in filesys.XATTR_PREFIX
			w.Header().Set(k, string(v))
		}
	}

	//Seaweed custom header are not visible to Vue or javascript
	seaweedHeaders := []string{}
	for header := range w.Header() {
		if strings.HasPrefix(header, "Seaweed-") {
			seaweedHeaders = append(seaweedHeaders, header)
		}
	}
	seaweedHeaders = append(seaweedHeaders, "Content-Disposition")
	w.Header().Set("Access-Control-Expose-Headers", strings.Join(seaweedHeaders, ","))

	//set tag count
	tagCount := 0
	for k := range entry.Extended {
		if strings.HasPrefix(k, xhttp.AmzObjectTagging+"-") {
			tagCount++
		}
	}
	if tagCount > 0 {
		w.Header().Set(xhttp.AmzTagCount, strconv.Itoa(tagCount))
	}

	setEtag(w, etag)

	filename := entry.Name()
	adjustPassthroughHeaders(w, r, filename)

	totalSize := int64(entry.Size())

	if r.Method == "HEAD" {
		w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
		return
	}

	if rangeReq := r.Header.Get("Range"); rangeReq == "" {
		ext := filepath.Ext(filename)
		if len(ext) > 0 {
			ext = strings.ToLower(ext)
		}
		width, height, mode, shouldResize := shouldResizeImages(ext, r)
		if shouldResize {
			data := mem.Allocate(int(totalSize))
			defer mem.Free(data)
			err := filer.ReadAll(data, fs.filer.MasterClient, entry.Chunks)
			if err != nil {
				glog.Errorf("failed to read %s: %v", path, err)
				w.WriteHeader(http.StatusNotModified)
				return
			}
			rs, _, _ := images.Resized(ext, bytes.NewReader(data), width, height, mode)
			io.Copy(w, rs)
			return
		}
	}

	processRangeRequest(r, w, totalSize, mimeType, func(writer io.Writer, offset int64, size int64) error {
		if offset+size <= int64(len(entry.Content)) {
			_, err := writer.Write(entry.Content[offset : offset+size])
			if err != nil {
				stats.FilerRequestCounter.WithLabelValues(stats.ErrorWriteEntry).Inc()
				glog.Errorf("failed to write entry content: %v", err)
			}
			return err
		}
		chunks := entry.Chunks
		if entry.IsInRemoteOnly() {
			dir, name := entry.FullPath.DirAndName()
			if resp, err := fs.CacheRemoteObjectToLocalCluster(context.Background(), &filer_pb.CacheRemoteObjectToLocalClusterRequest{
				Directory: dir,
				Name:      name,
			}); err != nil {
				stats.FilerRequestCounter.WithLabelValues(stats.ErrorReadCache).Inc()
				glog.Errorf("CacheRemoteObjectToLocalCluster %s: %v", entry.FullPath, err)
				return fmt.Errorf("cache %s: %v", entry.FullPath, err)
			} else {
				chunks = resp.Entry.Chunks
			}
		}

		err = filer.StreamContent(fs.filer.MasterClient, writer, chunks, offset, size)
		if err != nil {
			stats.FilerRequestCounter.WithLabelValues(stats.ErrorReadStream).Inc()
			glog.Errorf("failed to stream content %s: %v", r.URL, err)
		}
		return err
	})
}
<commit_msg>a little optimization<commit_after>package weed_server

import (
	"bytes"
	"context"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/util/mem"
	"io"
	"math"
	"mime"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/images"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	xhttp "github.com/chrislusf/seaweedfs/weed/s3api/http"
	"github.com/chrislusf/seaweedfs/weed/stats"
	"github.com/chrislusf/seaweedfs/weed/util"
)

// Validates the preconditions. Returns true if GET/HEAD operation should not proceed.
// Preconditions supported are:
//  If-Modified-Since
//  If-Unmodified-Since
//  If-Match
//  If-None-Match
func checkPreconditions(w http.ResponseWriter, r *http.Request, entry *filer.Entry) bool {

	etag := filer.ETagEntry(entry)
	/// When more than one conditional request header field is present in a
	/// request, the order in which the fields are evaluated becomes
	/// important.  In practice, the fields defined in this document are
	/// consistently implemented in a single, logical order, since "lost
	/// update" preconditions have more strict requirements than cache
	/// validation, a validated cache is more efficient than a partial
	/// response, and entity tags are presumed to be more accurate than date
	/// validators. https://tools.ietf.org/html/rfc7232#section-5
	if entry.Attr.Mtime.IsZero() {
		return false
	}
	w.Header().Set("Last-Modified", entry.Attr.Mtime.UTC().Format(http.TimeFormat))

	ifMatchETagHeader := r.Header.Get("If-Match")
	ifUnmodifiedSinceHeader := r.Header.Get("If-Unmodified-Since")
	if ifMatchETagHeader != "" {
		if util.CanonicalizeETag(etag) != util.CanonicalizeETag(ifMatchETagHeader) {
			w.WriteHeader(http.StatusPreconditionFailed)
			return true
		}
	} else if ifUnmodifiedSinceHeader != "" {
		if t, parseError := time.Parse(http.TimeFormat, ifUnmodifiedSinceHeader); parseError == nil {
			if t.Before(entry.Attr.Mtime) {
				w.WriteHeader(http.StatusPreconditionFailed)
				return true
			}
		}
	}

	ifNoneMatchETagHeader := r.Header.Get("If-None-Match")
	ifModifiedSinceHeader := r.Header.Get("If-Modified-Since")
	if ifNoneMatchETagHeader != "" {
		if util.CanonicalizeETag(etag) == util.CanonicalizeETag(ifNoneMatchETagHeader) {
			w.WriteHeader(http.StatusNotModified)
			return true
		}
	} else if ifModifiedSinceHeader != "" {
		if t, parseError := time.Parse(http.TimeFormat, ifModifiedSinceHeader); parseError == nil {
			if t.After(entry.Attr.Mtime) {
				w.WriteHeader(http.StatusNotModified)
				return true
			}
		}
	}

	return false
}

func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {

	path := r.URL.Path
	isForDirectory := strings.HasSuffix(path, "/")
	if isForDirectory && len(path) > 1 {
		path = path[:len(path)-1]
	}

	entry, err := fs.filer.FindEntry(context.Background(), util.FullPath(path))
	if err != nil {
		if path == "/" {
			fs.listDirectoryHandler(w, r)
			return
		}
		if err == filer_pb.ErrNotFound {
			glog.V(1).Infof("Not found %s: %v", path, err)
			stats.FilerRequestCounter.WithLabelValues(stats.ErrorReadNotFound).Inc()
			w.WriteHeader(http.StatusNotFound)
		} else {
			glog.Errorf("Internal %s: %v", path, err)
			stats.FilerRequestCounter.WithLabelValues(stats.ErrorReadInternal).Inc()
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}

	if entry.IsDirectory() {
		if fs.option.DisableDirListing {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		fs.listDirectoryHandler(w, r)
		return
	}

	if isForDirectory {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	query := r.URL.Query()
	if query.Get("metadata") == "true" {
		if query.Get("resolveManifest") == "true" {
			if entry.Chunks, _, err = filer.ResolveChunkManifest(
				fs.filer.MasterClient.GetLookupFileIdFunction(),
				entry.Chunks, 0, math.MaxInt64); err != nil {
				err = fmt.Errorf("failed to resolve chunk manifest, err: %s", err.Error())
				writeJsonError(w, r, http.StatusInternalServerError, err)
			}
		}
		writeJsonQuiet(w, r, http.StatusOK, entry)
		return
	}

	etag := filer.ETagEntry(entry)
	if checkPreconditions(w, r, entry) {
		return
	}

	w.Header().Set("Accept-Ranges", "bytes")

	// mime type
	mimeType := entry.Attr.Mime
	if mimeType == "" {
		if ext := filepath.Ext(entry.Name()); ext != "" {
			mimeType = mime.TypeByExtension(ext)
		}
	}
	if mimeType != "" {
		w.Header().Set("Content-Type", mimeType)
	}

	// print out the header from extended properties
	for k, v := range entry.Extended {
		if !strings.HasPrefix(k, "xattr-") {
			// "xattr-" prefix is set in filesys.XATTR_PREFIX
			w.Header().Set(k, string(v))
		}
	}

	//Seaweed custom header are not visible to Vue or javascript
	seaweedHeaders := []string{}
	for header := range w.Header() {
		if strings.HasPrefix(header, "Seaweed-") {
			seaweedHeaders = append(seaweedHeaders, header)
		}
	}
	seaweedHeaders = append(seaweedHeaders, "Content-Disposition")
	w.Header().Set("Access-Control-Expose-Headers", strings.Join(seaweedHeaders, ","))

	//set tag count
	tagCount := 0
	for k := range entry.Extended {
		if strings.HasPrefix(k, xhttp.AmzObjectTagging+"-") {
			tagCount++
		}
	}
	if tagCount > 0 {
		w.Header().Set(xhttp.AmzTagCount, strconv.Itoa(tagCount))
	}

	setEtag(w, etag)

	filename := entry.Name()
	adjustPassthroughHeaders(w, r, filename)

	totalSize := int64(entry.Size())

	if r.Method == "HEAD" {
		w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
		return
	}

	if rangeReq := r.Header.Get("Range"); rangeReq == "" {
		ext := filepath.Ext(filename)
		if len(ext) > 0 {
			ext = strings.ToLower(ext)
		}
		width, height, mode, shouldResize := shouldResizeImages(ext, r)
		if shouldResize {
			data := mem.Allocate(int(totalSize))
			defer mem.Free(data)
			err := filer.ReadAll(data, fs.filer.MasterClient, entry.Chunks)
			if err != nil {
				glog.Errorf("failed to read %s: %v", path, err)
				w.WriteHeader(http.StatusNotModified)
				return
			}
			rs, _, _ := images.Resized(ext, bytes.NewReader(data), width, height, mode)
			io.Copy(w, rs)
			return
		}
	}

	processRangeRequest(r, w, totalSize, mimeType, func(writer io.Writer, offset int64, size int64) error {
		if offset+size <= int64(len(entry.Content)) {
			_, err := writer.Write(entry.Content[offset : offset+size])
			if err != nil {
				stats.FilerRequestCounter.WithLabelValues(stats.ErrorWriteEntry).Inc()
				glog.Errorf("failed to write entry content: %v", err)
			}
			return err
		}
		chunks := entry.Chunks
		if entry.IsInRemoteOnly() {
			dir, name := entry.FullPath.DirAndName()
			if resp, err := fs.CacheRemoteObjectToLocalCluster(context.Background(), &filer_pb.CacheRemoteObjectToLocalClusterRequest{
				Directory: dir,
				Name:      name,
			}); err != nil {
				stats.FilerRequestCounter.WithLabelValues(stats.ErrorReadCache).Inc()
				glog.Errorf("CacheRemoteObjectToLocalCluster %s: %v", entry.FullPath, err)
				return fmt.Errorf("cache %s: %v", entry.FullPath, err)
			} else {
				chunks = resp.Entry.Chunks
			}
		}

		err = filer.StreamContent(fs.filer.MasterClient, writer, chunks, offset, size)
		if err != nil {
			stats.FilerRequestCounter.WithLabelValues(stats.ErrorReadStream).Inc()
			glog.Errorf("failed to stream content %s: %v", r.URL, err)
		}
		return err
	})
}
<|endoftext|>


<commit_before>/*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

gorcon/track (lee8oi)

*/

//
package track

import (
	"fmt"
	"strings"
)

type game struct {
	Name, Ranked, Balance, Map, Mode, Round, Players, Joining,
	Ntickets, Nsize, Rtickets, Rsize, Elapsed, Remaining string
}

//update parses the data string and updates the server data in the current game object.
func (g *game) update(data string) {
	splitLine := strings.Split(data, "\t")
	if len(splitLine) >= 27 {
		mode := strings.Split(splitLine[20], "_")[1]
		*g = game{
			Name:      splitLine[7],
			Ranked:    splitLine[25],
			Balance:   splitLine[24],
			Map:       mapName(splitLine[5]),
			Mode:      strings.ToUpper(mode),
			Round:     splitLine[31],
			Players:   splitLine[3],
			Joining:   splitLine[4],
			Ntickets:  splitLine[11],
			Nsize:     splitLine[26],
			Rtickets:  splitLine[16],
			Rsize:     splitLine[27],
			Elapsed:   splitLine[18],
			Remaining: splitLine[19],
		}
		if err := writeJSON("game.json", g); err != nil {
			fmt.Println(err)
		}
	}
}

//MapName returns the full name for the specified map.
func mapName(name string) string {
	switch name {
	case "dependant_day":
		return "Inland Invasion"
	case "dependant_day_night":
		return "Inland Invasion Night"
	case "heat":
		return "Riverside Rush"
	case "heat_snow":
		return "Riverside Rush Snow"
	case "lake":
		return "Buccaneer Bay"
	case "lake_night":
		return "Buccaneer Bay Night"
	case "lake_snow":
		return "Buccaneer Bay Snow"
	case "lunar":
		return "Lunar Landing"
	case "mayhem":
		return "Sunset Showdown"
	case "river":
		return "Fortress Frenzy"
	case "royal_rumble":
		return "Perilous Port Night"
	case "royal_rumble_day":
		return "Perilous Port Day"
	case "royal_rumble_snow":
		return "Perilous Port Snow"
	case "ruin":
		return "Midnight Mayhem"
	case "ruin_day":
		return "Morning Mayhem"
	case "ruin_snow":
		return "Midnight Mayhem Snow"
	case "seaside_skirmish":
		return "Seaside Skirmish"
	case "seaside_skirmish_night":
		return "Seaside Skirmish Night"
	case "smack2":
		return "Coastal Clash"
	case "smack2_night":
		return "Coastal Clash Night"
	case "smack2_snow":
		return "Coastal Clash Snow"
	case "village":
		return "Victory Village"
	case "village_snow":
		return "Victory Village Snow"
	case "wicked_wake":
		return "Wicked Wake"
	case "woodlands":
		return "Alpine Assault"
	case "woodlands_snow":
		return "Alpine Assault Snow"
	default:
		return name
	}
}
<commit_msg>Another attempt to fix an elusive index out of range error.<commit_after>/*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

gorcon/track (lee8oi)

*/

//
package track

import (
	"fmt"
	"strings"
)

type game struct {
	Name, Ranked, Balance, Map, Mode, Round, Players, Joining,
	Ntickets, Nsize, Rtickets, Rsize, Elapsed, Remaining string
}

//update parses the data string and updates the server data in the current game object.
func (g *game) update(data string) {
	splitLine := strings.Split(data, "\t")
	if len(splitLine) >= 27 {
		var mode string
		split := strings.Split(splitLine[20], "_")
		if len(split) > 1 {
			mode = split[1]
		} else {
			mode = data
		}
		*g = game{
			Name:      splitLine[7],
			Ranked:    splitLine[25],
			Balance:   splitLine[24],
			Map:       mapName(splitLine[5]),
			Mode:      strings.ToUpper(mode),
			Round:     splitLine[31],
			Players:   splitLine[3],
			Joining:   splitLine[4],
			Ntickets:  splitLine[11],
			Nsize:     splitLine[26],
			Rtickets:  splitLine[16],
			Rsize:     splitLine[27],
			Elapsed:   splitLine[18],
			Remaining: splitLine[19],
		}
		if err := writeJSON("game.json", g); err != nil {
			fmt.Println(err)
		}
	}
}

//MapName returns the full name for the specified map.
func mapName(name string) string {
	switch name {
	case "dependant_day":
		return "Inland Invasion"
	case "dependant_day_night":
		return "Inland Invasion Night"
	case "heat":
		return "Riverside Rush"
	case "heat_snow":
		return "Riverside Rush Snow"
	case "lake":
		return "Buccaneer Bay"
	case "lake_night":
		return "Buccaneer Bay Night"
	case "lake_snow":
		return "Buccaneer Bay Snow"
	case "lunar":
		return "Lunar Landing"
	case "mayhem":
		return "Sunset Showdown"
	case "river":
		return "Fortress Frenzy"
	case "royal_rumble":
		return "Perilous Port Night"
	case "royal_rumble_day":
		return "Perilous Port Day"
	case "royal_rumble_snow":
		return "Perilous Port Snow"
	case "ruin":
		return "Midnight Mayhem"
	case "ruin_day":
		return "Morning Mayhem"
	case "ruin_snow":
		return "Midnight Mayhem Snow"
	case "seaside_skirmish":
		return "Seaside Skirmish"
	case "seaside_skirmish_night":
		return "Seaside Skirmish Night"
	case "smack2":
		return "Coastal Clash"
	case "smack2_night":
		return "Coastal Clash Night"
	case "smack2_snow":
		return "Coastal Clash Snow"
	case "village":
		return "Victory Village"
	case "village_snow":
		return "Victory Village Snow"
	case "wicked_wake":
		return "Wicked Wake"
	case "woodlands":
		return "Alpine Assault"
	case "woodlands_snow":
		return "Alpine Assault Snow"
	default:
		return name
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package interrupts

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"math/big"
	"net"
	"net/http"
	"os"
	"sync"
	"syscall"
	"testing"
	"time"
)

// interrupt allows for tests to trigger an interrupt as needed
var interrupt = make(chan os.Signal, 1)

// this init will be executed before that in the code package,
// so we can inject our implementation of the interrupt channel
func init() {
	signalsLock.Lock()
	gracePeriod = 0 * time.Second
	signals = func() <-chan os.Signal {
		return interrupt
	}
	signalsLock.Unlock()
}

// instead of building a mechanism to reset/re-initialize the interrupt
// manager which would only be used in testing, we write an integration
// test that only fires the mock interrupt once
func TestInterrupts(t *testing.T) {
	// we need to lock around values used to test otherwise the test
	// goroutine will race with the workers
	lock := sync.Mutex{}

	ctx := Context()
	var ctxDone bool
	go func() {
		<-ctx.Done()

		lock.Lock()
		ctxDone = true
		lock.Unlock()
	}()

	var workDone bool
	var workCancelled bool
	work := func(ctx context.Context) {
		lock.Lock()
		workDone = true
		lock.Unlock()

		<-ctx.Done()

		lock.Lock()
		workCancelled = true
		lock.Unlock()
	}
	Run(work)

	// we cannot use httptest mocks for the tests here as they expect
	// to be started by the httptest package itself, not by a downstream
	// caller like the interrupts library
	var serverCalled bool
	var serverCancelled bool
	listener, err := net.Listen("tcp", "127.0.0.1:")
	if err != nil {
		t.Fatalf("could not listen on random port: %v", err)
	}
	if err := listener.Close(); err != nil {
		t.Fatalf("could close listener: %v", err)
	}
	server := &http.Server{Addr: listener.Addr().String(), Handler: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
		lock.Lock()
		serverCalled = true
		lock.Unlock()
	})}
	server.RegisterOnShutdown(func() {
		lock.Lock()
		serverCancelled = true
		lock.Unlock()
	})
	ListenAndServe(server, 1*time.Microsecond)
	if _, err := http.Get("http://" + listener.Addr().String()); err != nil {
		t.Errorf("could not reach server registered with ListenAndServe(): %v", err)
	}

	var tlsServerCalled bool
	var tlsServerCancelled bool
	tlsListener, err := net.Listen("tcp", "127.0.0.1:")
	if err != nil {
		t.Fatalf("could not listen on random port: %v", err)
	}
	if err := tlsListener.Close(); err != nil {
		t.Fatalf("could close listener: %v", err)
	}
	tlsServer := &http.Server{Addr: tlsListener.Addr().String(), Handler: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
		lock.Lock()
		tlsServerCalled = true
		lock.Unlock()
	})}
	tlsServer.RegisterOnShutdown(func() {
		lock.Lock()
		tlsServerCancelled = true
		lock.Unlock()
	})
	cert, key, err := generateCerts("127.0.0.1")
	if err != nil {
		t.Fatalf("could not generate cert and key for TLS server: %v", err)
	}
	ListenAndServeTLS(tlsServer, cert, key, 1*time.Microsecond)
	client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
	if _, err := client.Get("https://" + tlsListener.Addr().String()); err != nil {
		t.Errorf("could not reach server registered with ListenAndServeTLS(): %v", err)
	}

	var intervalCalls int
	interval := func() time.Duration {
		lock.Lock()
		intervalCalls++
		lock.Unlock()
		if intervalCalls > 2 {
			return 10 * time.Hour
		}
		return 1 * time.Nanosecond
	}
	var tickCalls int
	tick := func() {
		lock.Lock()
		tickCalls++
		lock.Unlock()
	}
	Tick(tick, interval)
	// writing a test that functions correctly here without being susceptible
	// to timing flakes is challenging. Using time.Sleep like this does have
	// that downside, but the sleep time is many orders of magnitude higher
	// than the tick intervals and the amount of time taken to execute the
	// test as well, so it is going to be exceedingly rare that scheduling of
	// the test process will cause a flake here from timing. The test cannot
	// use synchronized approaches to waiting here as we do not know how long
	// we must wait. The test must have enough time to ask for the interval
	// as many times as we expect it to, but if we only wait for that we fail
	// to catch the cases where the interval is requested too many times.
	time.Sleep(100 * time.Millisecond)

	var onInterruptCalled bool
	OnInterrupt(func() {
		lock.Lock()
		onInterruptCalled = true
		lock.Unlock()
	})

	done := sync.WaitGroup{}
	done.Add(1)
	go func() {
		WaitForGracefulShutdown()
		done.Done()
	}()

	if onInterruptCalled {
		t.Error("work registered with OnInterrupt() was executed before interrupt")
	}

	// trigger the interrupt
	interrupt <- syscall.Signal(1)
	// wait for graceful shutdown to occur
	done.Wait()

	lock.Lock()
	if !ctxDone {
		t.Error("context from Context() was not cancelled on interrupt")
	}
	if !workDone {
		t.Error("work registered with Run() was not executed")
	}
	if !workCancelled {
		t.Error("work registered with Run() was not cancelled on interrupt")
	}
	if !serverCalled {
		t.Error("server registered with ListenAndServe() was not serving")
	}
	if !serverCancelled {
		t.Error("server registered with ListenAndServe() was not cancelled on interrupt")
	}
	if !tlsServerCalled {
		t.Error("server registered with ListenAndServeTLS() was not serving")
	}
	if !tlsServerCancelled {
		t.Error("server registered with ListenAndServeTLS() was not cancelled on interrupt")
	}
	if tickCalls != 2 {
		t.Errorf("work registered with Tick() was called %d times, not %d; interval was requested %d times", tickCalls, 2, intervalCalls)
	}
	if !onInterruptCalled {
		t.Error("work registered with OnInterrupt() was not executed on interrupt")
	}
	lock.Unlock()
}

func generateCerts(url string) (string, string, error) {
	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return "", "", fmt.Errorf("failed to generate private key: %v", err)
	}

	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return "", "", fmt.Errorf("failed to generate serial number: %s", err)
	}

	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"Acme Co"},
		},
		NotBefore: time.Now(),
		NotAfter:  time.Now().Add(1 * time.Hour),

		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,

		IPAddresses: []net.IP{net.ParseIP(url)},
	}

	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
	if err != nil {
		return "", "", fmt.Errorf("failed to create certificate: %s", err)
	}

	certOut, err := ioutil.TempFile("", "cert.pem")
	if err != nil {
		return "", "", fmt.Errorf("failed to open cert.pem for writing: %s", err)
	}
	if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
		return "", "", fmt.Errorf("failed to write data to cert.pem: %s", err)
	}
	if err := certOut.Close(); err != nil {
		return "", "", fmt.Errorf("error closing cert.pem: %s", err)
	}

	keyOut, err := ioutil.TempFile("", "key.pem")
	if err != nil {
		return "", "", fmt.Errorf("failed to open key.pem for writing: %v", err)
	}
	privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
	if err != nil {
		return "", "", fmt.Errorf("unable to marshal private key: %v", err)
	}
	if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
		return "", "", fmt.Errorf("failed to write data to key.pem: %s", err)
	}
	if err := keyOut.Close(); err != nil {
		return "", "", fmt.Errorf("error closing key.pem: %s", err)
	}
	if err := os.Chmod(keyOut.Name(), 0600); err != nil {
		return "", "", fmt.Errorf("could not change permissions on key.pem: %v", err)
	}
	return certOut.Name(), keyOut.Name(), nil
}
<commit_msg>Be more lenient with server starts in interrupts test<commit_after>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package interrupts

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"math/big"
	"net"
	"net/http"
	"os"
	"sync"
	"syscall"
	"testing"
	"time"
)

// interrupt allows for tests to trigger an interrupt as needed
var interrupt = make(chan os.Signal, 1)

// this init will be executed before that in the code package,
// so we can inject our implementation of the interrupt channel
func init() {
	signalsLock.Lock()
	gracePeriod = time.Second
	signals = func() <-chan os.Signal {
		return interrupt
	}
	signalsLock.Unlock()
}

// instead of building a mechanism to reset/re-initialize the interrupt
// manager which would only be used in testing, we write an integration
// test that only fires the mock interrupt once
func TestInterrupts(t *testing.T) {
	// we need to lock around values used to test otherwise the test
	// goroutine will race with the workers
	lock := sync.Mutex{}

	ctx := Context()
	var ctxDone bool
	go func() {
		<-ctx.Done()

		lock.Lock()
		ctxDone = true
		lock.Unlock()
	}()

	var workDone bool
	var workCancelled bool
	work := func(ctx context.Context) {
		lock.Lock()
		workDone = true
		lock.Unlock()

		<-ctx.Done()

		lock.Lock()
		workCancelled = true
		lock.Unlock()
	}
	Run(work)

	// we cannot use httptest mocks for the tests here as they expect
	// to be started by the httptest package itself, not by a downstream
	// caller like the interrupts library
	var serverCalled bool
	var serverCancelled bool
	listener, err := net.Listen("tcp", "127.0.0.1:")
	if err != nil {
		t.Fatalf("could not listen on random port: %v", err)
	}
	if err := listener.Close(); err != nil {
		t.Fatalf("could close listener: %v", err)
	}
	server := &http.Server{Addr: listener.Addr().String(), Handler: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
		lock.Lock()
		serverCalled = true
		lock.Unlock()
	})}
	server.RegisterOnShutdown(func() {
		lock.Lock()
		serverCancelled = true
		lock.Unlock()
	})
	ListenAndServe(server, time.Second)
	// wait for the server to start
	time.Sleep(100 * time.Millisecond)
	if _, err := http.Get("http://" + listener.Addr().String()); err != nil {
		t.Errorf("could not reach server registered with ListenAndServe(): %v", err)
	}

	var tlsServerCalled bool
	var tlsServerCancelled bool
	tlsListener, err := net.Listen("tcp", "127.0.0.1:")
	if err != nil {
		t.Fatalf("could not listen on random port: %v", err)
	}
	if err := tlsListener.Close(); err != nil {
		t.Fatalf("could close listener: %v", err)
	}
	tlsServer := &http.Server{Addr: tlsListener.Addr().String(), Handler: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
		lock.Lock()
		tlsServerCalled = true
		lock.Unlock()
	})}
	tlsServer.RegisterOnShutdown(func() {
		lock.Lock()
		tlsServerCancelled = true
		lock.Unlock()
	})
	cert, key, err := generateCerts("127.0.0.1")
	if err != nil {
		t.Fatalf("could not generate cert and key for TLS server: %v", err)
	}
	ListenAndServeTLS(tlsServer, cert, key, time.Second)
	client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
	// wait for the server to start
	time.Sleep(100 * time.Millisecond)
	if _, err := client.Get("https://" + tlsListener.Addr().String()); err != nil {
		t.Errorf("could not reach server registered with ListenAndServeTLS(): %v", err)
	}

	var intervalCalls int
	interval := func() time.Duration {
		lock.Lock()
		intervalCalls++
		lock.Unlock()
		if intervalCalls > 2 {
			return 10 * time.Hour
		}
		return 1 * time.Nanosecond
	}
	var tickCalls int
	tick := func() {
		lock.Lock()
		tickCalls++
		lock.Unlock()
	}
	Tick(tick, interval)
	// writing a test that functions correctly here without being susceptible
	// to timing flakes is challenging. Using time.Sleep like this does have
	// that downside, but the sleep time is many orders of magnitude higher
	// than the tick intervals and the amount of time taken to execute the
	// test as well, so it is going to be exceedingly rare that scheduling of
	// the test process will cause a flake here from timing. The test cannot
	// use synchronized approaches to waiting here as we do not know how long
	// we must wait. The test must have enough time to ask for the interval
	// as many times as we expect it to, but if we only wait for that we fail
	// to catch the cases where the interval is requested too many times.
	time.Sleep(100 * time.Millisecond)

	var onInterruptCalled bool
	OnInterrupt(func() {
		lock.Lock()
		onInterruptCalled = true
		lock.Unlock()
	})

	done := sync.WaitGroup{}
	done.Add(1)
	go func() {
		WaitForGracefulShutdown()
		done.Done()
	}()

	if onInterruptCalled {
		t.Error("work registered with OnInterrupt() was executed before interrupt")
	}

	// trigger the interrupt
	interrupt <- syscall.Signal(1)
	// wait for graceful shutdown to occur
	done.Wait()

	lock.Lock()
	if !ctxDone {
		t.Error("context from Context() was not cancelled on interrupt")
	}
	if !workDone {
		t.Error("work registered with Run() was not executed")
	}
	if !workCancelled {
		t.Error("work registered with Run() was not cancelled on interrupt")
	}
	if !serverCalled {
		t.Error("server registered with ListenAndServe() was not serving")
	}
	if !serverCancelled {
		t.Error("server registered with ListenAndServe() was not cancelled on interrupt")
	}
	if !tlsServerCalled {
		t.Error("server registered with ListenAndServeTLS() was not serving")
	}
	if !tlsServerCancelled {
		t.Error("server registered with ListenAndServeTLS() was not cancelled on interrupt")
	}
	if tickCalls != 2 {
		t.Errorf("work registered with Tick() was called %d times, not %d; interval was requested %d times", tickCalls, 2, intervalCalls)
	}
	if !onInterruptCalled {
		t.Error("work registered with OnInterrupt() was not executed on interrupt")
	}
	lock.Unlock()
}

func generateCerts(url string) (string, string, error) {
	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return "", "", fmt.Errorf("failed to generate private key: %v", err)
	}

	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return "", "", fmt.Errorf("failed to generate serial number: %s", err)
	}

	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"Acme Co"},
		},
		NotBefore: time.Now(),
		NotAfter:  time.Now().Add(1 * time.Hour),

		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,

		IPAddresses: []net.IP{net.ParseIP(url)},
	}

	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
	if err != nil {
		return "", "", fmt.Errorf("failed to create certificate: %s", err)
	}

	certOut, err := ioutil.TempFile("", "cert.pem")
	if err != nil {
		return "", "", fmt.Errorf("failed to open cert.pem for writing: %s", err)
	}
	if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
		return "", "", fmt.Errorf("failed to write data to cert.pem: %s", err)
	}
	if err := certOut.Close(); err != nil {
		return "", "", fmt.Errorf("error closing cert.pem: %s", err)
	}

	keyOut, err := ioutil.TempFile("", "key.pem")
	if err != nil {
		return "", "", fmt.Errorf("failed to open key.pem for writing: %v", err)
	}
	privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
	if err != nil {
		return "", "", fmt.Errorf("unable to marshal private key: %v", err)
	}
	if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
		return "", "", fmt.Errorf("failed to write data to key.pem: %s", err)
	}
	if err := keyOut.Close(); err != nil {
		return "", "", fmt.Errorf("error closing key.pem: %s", err)
	}
	if err := os.Chmod(keyOut.Name(), 0600); err != nil {
		return "", "", fmt.Errorf("could not change permissions on key.pem: %v", err)
	}
	return certOut.Name(), keyOut.Name(), nil
}
<|endoftext|>