<commit_before>package model

import (
	"fmt"
	"strings"

	"github.com/taskcluster/taskcluster-client-java/codegenerator/utils"
)

//////////////////////////////////////////////////////////////////
//
// From: http://schemas.taskcluster.net/base/v1/api-reference.json
//
//////////////////////////////////////////////////////////////////

type API struct {
	Version     interface{} `json:"version"`
	Title       string      `json:"title"`
	Description string      `json:"description"`
	BaseURL     string      `json:"baseUrl"`
	Entries     []APIEntry  `json:"entries"`

	apiDef *APIDefinition
}

func (api *API) String() string {
	var result string = fmt.Sprintf(
		"Version     = '%v'\n"+
			"Title       = '%v'\n"+
			"Description = '%v'\n"+
			"Base URL    = '%v'\n",
		api.Version, api.Title, api.Description, api.BaseURL)
	for i, entry := range api.Entries {
		result += fmt.Sprintf("Entry %-6v=\n%v", i, entry.String())
	}
	return result
}

func (api *API) postPopulate(apiDef *APIDefinition) {

	// make sure each entry defined for this API has a unique generated method name
	methods := make(map[string]bool)

	for i := range api.Entries {
		api.Entries[i].Parent = api
		api.Entries[i].MethodName = utils.NormaliseLower(api.Entries[i].Name, methods)
		api.Entries[i].postPopulate(apiDef)
	}
}

func (api *API) generateAPICode(apiName string) string {
	className := strings.Title(apiName)
	packageName := strings.ToLower(apiName)
	content := `package org.mozilla.taskcluster.client.` + packageName + `;

import org.mozilla.taskcluster.client.APICallFailure;
import org.mozilla.taskcluster.client.CallSummary;
import org.mozilla.taskcluster.client.Credentials;
import org.mozilla.taskcluster.client.EmptyPayload;
import org.mozilla.taskcluster.client.TaskClusterRequestHandler;

`
	comment := "/**\n"
	if api.Description != "" {
		comment += utils.Indent(api.Description, " * ")
	}
	if len(comment) >= 1 && comment[len(comment)-1:] != "\n" {
		comment += "\n"
	}
	comment += " *\n"
	comment += fmt.Sprintf(" * See: %v\n", api.apiDef.DocRoot)
	comment += " */\n"
	content += comment
	content += "public class " + className + ` extends TaskClusterRequestHandler {

    protected static final String defaultBaseURL = "` + api.BaseURL + `";

    public ` + className + `(Credentials credentials) {
        super(credentials, defaultBaseURL);
    }

    public ` + className + `(Credentials credentials, String baseURL) {
        super(credentials, baseURL);
    }

    public ` + className + `(String clientId, String accessToken) {
        super(new Credentials(clientId, accessToken), defaultBaseURL);
    }

    public ` + className + `(String clientId, String accessToken, String certificate) {
        super(new Credentials(clientId, accessToken, certificate), defaultBaseURL);
    }

    public ` + className + `(String baseURL) {
        super(baseURL);
    }

    public ` + className + `() {
        super(defaultBaseURL);
    }
`
	for _, entry := range api.Entries {
		content += entry.generateAPICode(apiName)
	}
	content += "}"
	return content
}

func (api *API) setAPIDefinition(apiDef *APIDefinition) {
	api.apiDef = apiDef
}

type APIEntry struct {
	Type        string     `json:"type"`
	Method      string     `json:"method"`
	Route       string     `json:"route"`
	Args        []string   `json:"args"`
	Name        string     `json:"name"`
	Scopes      [][]string `json:"scopes"`
	Input       string     `json:"input"`
	Output      string     `json:"output"`
	Title       string     `json:"title"`
	Description string     `json:"description"`

	MethodName string
	Parent     *API
}

func (entry *APIEntry) postPopulate(apiDef *APIDefinition) {
	if entry.Input != "" {
		entry.Parent.apiDef.cacheJsonSchema(&entry.Input)
		entry.Parent.apiDef.schemas[entry.Input].IsInputSchema = true
	}
	if entry.Output != "" {
		entry.Parent.apiDef.cacheJsonSchema(&entry.Output)
		entry.Parent.apiDef.schemas[entry.Output].IsOutputSchema = true
	}
}

func (entry *APIEntry) String() string {
	return fmt.Sprintf(
		"    Entry Type        = '%v'\n"+
			"    Entry Method      = '%v'\n"+
			"    Entry Route       = '%v'\n"+
			"    Entry Args        = '%v'\n"+
			"    Entry Name        = '%v'\n"+
			"    Entry Scopes      = '%v'\n"+
			"    Entry Input       = '%v'\n"+
			"    Entry Output      = '%v'\n"+
			"    Entry Title       = '%v'\n"+
			"    Entry Description = '%v'\n",
		entry.Type, entry.Method, entry.Route, entry.Args,
		entry.Name, entry.Scopes, entry.Input, entry.Output,
		entry.Title, entry.Description)
}

func (entry *APIEntry) generateAPICode(apiName string) string {
	comment := "\n    /**\n"
	if entry.Description != "" {
		comment += utils.Indent(entry.Description, "     * ")
	}
	if len(comment) >= 1 && comment[len(comment)-1:] != "\n" {
		comment += "\n"
	}
	comment += "     *\n"
	comment += fmt.Sprintf("     * See %v/#%v\n", entry.Parent.apiDef.DocRoot, entry.Name)
	comment += "     */\n"
	inputParams := ""
	if len(entry.Args) > 0 {
		inputParams += "String " + strings.Join(entry.Args, ", String ")
	}

	apiArgsPayload := "null"
	if entry.Input != "" {
		apiArgsPayload = "payload"
		p := entry.Parent.apiDef.schemas[entry.Input].TypeName + " payload"
		if inputParams == "" {
			inputParams = p
		} else {
			inputParams += ", " + p
		}
	}

	requestType := "EmptyPayload"
	if entry.Input != "" {
		requestType = entry.Parent.apiDef.schemas[entry.Input].TypeName
	}
	responseType := "EmptyPayload"
	if entry.Output != "" {
		responseType = entry.Parent.apiDef.schemas[entry.Output].TypeName
		if entry.Parent.apiDef.schemas[entry.Output].Type != nil && *entry.Parent.apiDef.schemas[entry.Output].Type == "array" {
			responseType += "[]"
		}
	}
	returnType := "CallSummary<" + requestType + ", " + responseType + ">"

	content := comment
	content += "    public " + returnType + " " + entry.MethodName + "(" + inputParams + ") throws APICallFailure {\n"
	content += "        return apiCall(" + apiArgsPayload + ", \"" + strings.ToUpper(entry.Method) + "\", \"" + strings.Replace(strings.Replace(entry.Route, "<", "\" + uriEncode(", -1), ">", ") + \"", -1) + "\", " + responseType + ".class);\n"
	content += "    }\n"
	// can remove any code that added an empty string to another string
	return strings.Replace(content, ` + ""`, "", -1)
}
<commit_msg>Corrected links to docs<commit_after>package model

import (
	"fmt"
	"strings"

	"github.com/taskcluster/taskcluster-client-java/codegenerator/utils"
)

//////////////////////////////////////////////////////////////////
//
// From: http://schemas.taskcluster.net/base/v1/api-reference.json
//
//////////////////////////////////////////////////////////////////

type API struct {
	Version     interface{} `json:"version"`
	Title       string      `json:"title"`
	Description string      `json:"description"`
	BaseURL     string      `json:"baseUrl"`
	Entries     []APIEntry  `json:"entries"`

	apiDef *APIDefinition
}

func (api *API) String() string {
	var result string = fmt.Sprintf(
		"Version     = '%v'\n"+
			"Title       = '%v'\n"+
			"Description = '%v'\n"+
			"Base URL    = '%v'\n",
		api.Version, api.Title, api.Description, api.BaseURL)
	for i, entry := range api.Entries {
		result += fmt.Sprintf("Entry %-6v=\n%v", i, entry.String())
	}
	return result
}

func (api *API) postPopulate(apiDef *APIDefinition) {

	// make sure each entry defined for this API has a unique generated method name
	methods := make(map[string]bool)

	for i := range api.Entries {
		api.Entries[i].Parent = api
		api.Entries[i].MethodName = utils.NormaliseLower(api.Entries[i].Name, methods)
		api.Entries[i].postPopulate(apiDef)
	}
}

func (api *API) generateAPICode(apiName string) string {
	className := strings.Title(apiName)
	packageName := strings.ToLower(apiName)
	content := `package org.mozilla.taskcluster.client.` + packageName + `;

import org.mozilla.taskcluster.client.APICallFailure;
import org.mozilla.taskcluster.client.CallSummary;
import org.mozilla.taskcluster.client.Credentials;
import org.mozilla.taskcluster.client.EmptyPayload;
import org.mozilla.taskcluster.client.TaskClusterRequestHandler;

`
	comment := "/**\n"
	if api.Description != "" {
		comment += utils.Indent(api.Description, " * ")
	}
	if len(comment) >= 1 && comment[len(comment)-1:] != "\n" {
		comment += "\n"
	}
	comment += " *\n"
	comment += fmt.Sprintf(" * See: %v\n", api.apiDef.DocRoot)
	comment += " */\n"
	content += comment
	content += "public class " + className + ` extends TaskClusterRequestHandler {

    protected static final String defaultBaseURL = "` + api.BaseURL + `";

    public ` + className + `(Credentials credentials) {
        super(credentials, defaultBaseURL);
    }

    public ` + className + `(Credentials credentials, String baseURL) {
        super(credentials, baseURL);
    }

    public ` + className + `(String clientId, String accessToken) {
        super(new Credentials(clientId, accessToken), defaultBaseURL);
    }

    public ` + className + `(String clientId, String accessToken, String certificate) {
        super(new Credentials(clientId, accessToken, certificate), defaultBaseURL);
    }

    public ` + className + `(String baseURL) {
        super(baseURL);
    }

    public ` + className + `() {
        super(defaultBaseURL);
    }
`
	for _, entry := range api.Entries {
		content += entry.generateAPICode(apiName)
	}
	content += "}"
	return content
}

func (api *API) setAPIDefinition(apiDef *APIDefinition) {
	api.apiDef = apiDef
}

type APIEntry struct {
	Type        string     `json:"type"`
	Method      string     `json:"method"`
	Route       string     `json:"route"`
	Args        []string   `json:"args"`
	Name        string     `json:"name"`
	Scopes      [][]string `json:"scopes"`
	Input       string     `json:"input"`
	Output      string     `json:"output"`
	Title       string     `json:"title"`
	Description string     `json:"description"`

	MethodName string
	Parent     *API
}

func (entry *APIEntry) postPopulate(apiDef *APIDefinition) {
	if entry.Input != "" {
		entry.Parent.apiDef.cacheJsonSchema(&entry.Input)
		entry.Parent.apiDef.schemas[entry.Input].IsInputSchema = true
	}
	if entry.Output != "" {
		entry.Parent.apiDef.cacheJsonSchema(&entry.Output)
		entry.Parent.apiDef.schemas[entry.Output].IsOutputSchema = true
	}
}

func (entry *APIEntry) String() string {
	return fmt.Sprintf(
		"    Entry Type        = '%v'\n"+
			"    Entry Method      = '%v'\n"+
			"    Entry Route       = '%v'\n"+
			"    Entry Args        = '%v'\n"+
			"    Entry Name        = '%v'\n"+
			"    Entry Scopes      = '%v'\n"+
			"    Entry Input       = '%v'\n"+
			"    Entry Output      = '%v'\n"+
			"    Entry Title       = '%v'\n"+
			"    Entry Description = '%v'\n",
		entry.Type, entry.Method, entry.Route, entry.Args,
		entry.Name, entry.Scopes, entry.Input, entry.Output,
		entry.Title, entry.Description)
}

func (entry *APIEntry) generateAPICode(apiName string) string {
	comment := "\n    /**\n"
	if entry.Description != "" {
		comment += utils.Indent(entry.Description, "     * ")
	}
	if len(comment) >= 1 && comment[len(comment)-1:] != "\n" {
		comment += "\n"
	}
	comment += "     *\n"
	comment += fmt.Sprintf("     * See %v#%v\n", entry.Parent.apiDef.DocRoot, entry.Name)
	comment += "     */\n"
	inputParams := ""
	if len(entry.Args) > 0 {
		inputParams += "String " + strings.Join(entry.Args, ", String ")
	}

	apiArgsPayload := "null"
	if entry.Input != "" {
		apiArgsPayload = "payload"
		p := entry.Parent.apiDef.schemas[entry.Input].TypeName + " payload"
		if inputParams == "" {
			inputParams = p
		} else {
			inputParams += ", " + p
		}
	}

	requestType := "EmptyPayload"
	if entry.Input != "" {
		requestType = entry.Parent.apiDef.schemas[entry.Input].TypeName
	}
	responseType := "EmptyPayload"
	if entry.Output != "" {
		responseType = entry.Parent.apiDef.schemas[entry.Output].TypeName
		if entry.Parent.apiDef.schemas[entry.Output].Type != nil && *entry.Parent.apiDef.schemas[entry.Output].Type == "array" {
			responseType += "[]"
		}
	}
	returnType := "CallSummary<" + requestType + ", " + responseType + ">"

	content := comment
	content += "    public " + returnType + " " + entry.MethodName + "(" + inputParams + ") throws APICallFailure {\n"
	content += "        return apiCall(" + apiArgsPayload + ", \"" + strings.ToUpper(entry.Method) + "\", \"" + strings.Replace(strings.Replace(entry.Route, "<", "\" + uriEncode(", -1), ">", ") + \"", -1) + "\", " + responseType + ".class);\n"
	content += "    }\n"
	// can remove any code that added an empty string to another string
	return strings.Replace(content, ` + ""`, "", -1)
}
<|endoftext|>


<commit_before>/*
   Copyright The containerd 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 main

import (
	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/reflect/protoreflect"
)

type generator struct {
	out *protogen.GeneratedFile
}

func newGenerator(out *protogen.GeneratedFile) *generator {
	gen := generator{out: out}
	return &gen
}

func (gen *generator) genFieldMethod(m *protogen.Message) {
	p := gen.out

	p.P("// Field returns the value for the given fieldpath as a string, if defined.")
	p.P("// If the value is not defined, the second value will be false.")
	p.P("func (m *", m.GoIdent, ") Field(fieldpath []string) (string, bool) {")

	var (
		fields    []*protogen.Field
		unhandled []*protogen.Field
	)

	for _, f := range m.Fields {
		if f.Desc.Kind() == protoreflect.BoolKind ||
			f.Desc.Kind() == protoreflect.StringKind ||
			isLabelsField(f) || isAnyField(f) || isMessageField(f) {
			fields = append(fields, f)
		} else {
			unhandled = append(unhandled, f)
		}

	}

	if len(fields) > 0 {
		p.P("if len(fieldpath) == 0 {")
		p.P(`return "", false`)
		p.P("}")

		p.P("switch fieldpath[0] {")

		for _, f := range unhandled {
			p.P("// unhandled: ", f.Desc.Name())
		}

		for _, f := range fields {
			p.P(`case "`, f.Desc.Name(), `":`)
			switch {
			case isLabelsField(f):
				stringsJoin := gen.out.QualifiedGoIdent(protogen.GoIdent{
					GoImportPath: "strings",
					GoName:       "Join",
				})

				p.P(`// Labels fields have been special-cased by name. If this breaks,`)
				p.P(`// add better special casing to fieldpath plugin.`)
				p.P("if len(m.", f.GoName, ") == 0 {")
				p.P(`return "", false`)
				p.P("}")
				p.P("value, ok := m.", f.GoName, "[", stringsJoin, `(fieldpath[1:], ".")]`)
				p.P("return value, ok")
			case isAnyField(f):
				typeurlUnmarshalAny := gen.out.QualifiedGoIdent(protogen.GoIdent{
					GoImportPath: "github.com/containerd/typeurl",
					GoName:       "UnmarshalAny",
				})

				p.P("decoded, err := ", typeurlUnmarshalAny, "(m.", f.GoName, ")")
				p.P("if err != nil {")
				p.P(`return "", false`)
				p.P("}")
				p.P("adaptor, ok := decoded.(interface{ Field([]string) (string, bool) })")
				p.P("if !ok {")
				p.P(`return "", false`)
				p.P("}")
				p.P("return adaptor.Field(fieldpath[1:])")
			case isMessageField(f):
				p.P(`// NOTE(stevvooe): This is probably not correct in many cases.`)
				p.P(`// We assume that the target message also implements the Field`)
				p.P(`// method, which isn't likely true in a lot of cases.`)
				p.P(`//`)
				p.P(`// If you have a broken build and have found this comment,`)
				p.P(`// you may be closer to a solution.`)
				p.P("if m.", f.GoName, " == nil {")
				p.P(`return "", false`)
				p.P("}")
				p.P("return m.", f.GoName, ".Field(fieldpath[1:])")
			case f.Desc.Kind() == protoreflect.StringKind:
				p.P("return string(m.", f.GoName, "), len(m.", f.GoName, ") > 0")
			case f.Desc.Kind() == protoreflect.BoolKind:
				fmtSprint := gen.out.QualifiedGoIdent(protogen.GoIdent{
					GoImportPath: "fmt",
					GoName:       "Sprint",
				})

				p.P("return ", fmtSprint, "(m.", f.GoName, "), true")
			}
		}

		p.P("}")
	} else {
		for _, f := range unhandled {
			p.P("// unhandled: ", f.Desc.Name())
		}
	}

	p.P(`return "", false`)
	p.P("}")
}

func isMessageField(f *protogen.Field) bool {
	return f.Desc.Kind() == protoreflect.MessageKind && f.Desc.Cardinality() != protoreflect.Repeated && f.Message.GoIdent.GoName != "Timestamp"
}

func isLabelsField(f *protogen.Field) bool {
	return f.Desc.Kind() == protoreflect.MessageKind && f.Desc.Name() == "labels"
}

func isAnyField(f *protogen.Field) bool {
	return f.Desc.Kind() == protoreflect.MessageKind && f.Message.GoIdent.GoName == "Any"
}

func collectChildlen(parent *protogen.Message) ([]*protogen.Message, error) {
	var children []*protogen.Message
	for _, child := range parent.Messages {
		if child.Desc.IsMapEntry() {
			continue
		}
		children = append(children, child)

		xs, err := collectChildlen(child)
		if err != nil {
			return nil, err
		}
		children = append(children, xs...)
	}
	return children, nil
}

func generate(plugin *protogen.Plugin, input *protogen.File) error {
	file := plugin.NewGeneratedFile(input.GeneratedFilenamePrefix+"_fieldpath.pb.go", input.GoImportPath)
	file.P("// Code generated by protoc-gen-go-fieldpath. DO NOT EDIT.")
	file.P("// source: ", input.Desc.Path())
	file.P("package ", input.GoPackageName)

	gen := newGenerator(file)

	var messages []*protogen.Message
	for _, m := range input.Messages {
		messages = append(messages, m)
		children, err := collectChildlen(m)
		if err != nil {
			return err
		}
		messages = append(messages, children...)
	}

	for _, m := range messages {
		gen.genFieldMethod(m)
	}
	return nil
}
<commit_msg>Don't generate a Go file, if that would be empty<commit_after>/*
   Copyright The containerd 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 main

import (
	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/reflect/protoreflect"
)

type generator struct {
	out *protogen.GeneratedFile
}

func newGenerator(out *protogen.GeneratedFile) *generator {
	gen := generator{out: out}
	return &gen
}

func (gen *generator) genFieldMethod(m *protogen.Message) {
	p := gen.out

	p.P("// Field returns the value for the given fieldpath as a string, if defined.")
	p.P("// If the value is not defined, the second value will be false.")
	p.P("func (m *", m.GoIdent, ") Field(fieldpath []string) (string, bool) {")

	var (
		fields    []*protogen.Field
		unhandled []*protogen.Field
	)

	for _, f := range m.Fields {
		if f.Desc.Kind() == protoreflect.BoolKind ||
			f.Desc.Kind() == protoreflect.StringKind ||
			isLabelsField(f) || isAnyField(f) || isMessageField(f) {
			fields = append(fields, f)
		} else {
			unhandled = append(unhandled, f)
		}

	}

	if len(fields) > 0 {
		p.P("if len(fieldpath) == 0 {")
		p.P(`return "", false`)
		p.P("}")

		p.P("switch fieldpath[0] {")

		for _, f := range unhandled {
			p.P("// unhandled: ", f.Desc.Name())
		}

		for _, f := range fields {
			p.P(`case "`, f.Desc.Name(), `":`)
			switch {
			case isLabelsField(f):
				stringsJoin := gen.out.QualifiedGoIdent(protogen.GoIdent{
					GoImportPath: "strings",
					GoName:       "Join",
				})

				p.P(`// Labels fields have been special-cased by name. If this breaks,`)
				p.P(`// add better special casing to fieldpath plugin.`)
				p.P("if len(m.", f.GoName, ") == 0 {")
				p.P(`return "", false`)
				p.P("}")
				p.P("value, ok := m.", f.GoName, "[", stringsJoin, `(fieldpath[1:], ".")]`)
				p.P("return value, ok")
			case isAnyField(f):
				typeurlUnmarshalAny := gen.out.QualifiedGoIdent(protogen.GoIdent{
					GoImportPath: "github.com/containerd/typeurl",
					GoName:       "UnmarshalAny",
				})

				p.P("decoded, err := ", typeurlUnmarshalAny, "(m.", f.GoName, ")")
				p.P("if err != nil {")
				p.P(`return "", false`)
				p.P("}")
				p.P("adaptor, ok := decoded.(interface{ Field([]string) (string, bool) })")
				p.P("if !ok {")
				p.P(`return "", false`)
				p.P("}")
				p.P("return adaptor.Field(fieldpath[1:])")
			case isMessageField(f):
				p.P(`// NOTE(stevvooe): This is probably not correct in many cases.`)
				p.P(`// We assume that the target message also implements the Field`)
				p.P(`// method, which isn't likely true in a lot of cases.`)
				p.P(`//`)
				p.P(`// If you have a broken build and have found this comment,`)
				p.P(`// you may be closer to a solution.`)
				p.P("if m.", f.GoName, " == nil {")
				p.P(`return "", false`)
				p.P("}")
				p.P("return m.", f.GoName, ".Field(fieldpath[1:])")
			case f.Desc.Kind() == protoreflect.StringKind:
				p.P("return string(m.", f.GoName, "), len(m.", f.GoName, ") > 0")
			case f.Desc.Kind() == protoreflect.BoolKind:
				fmtSprint := gen.out.QualifiedGoIdent(protogen.GoIdent{
					GoImportPath: "fmt",
					GoName:       "Sprint",
				})

				p.P("return ", fmtSprint, "(m.", f.GoName, "), true")
			}
		}

		p.P("}")
	} else {
		for _, f := range unhandled {
			p.P("// unhandled: ", f.Desc.Name())
		}
	}

	p.P(`return "", false`)
	p.P("}")
}

func isMessageField(f *protogen.Field) bool {
	return f.Desc.Kind() == protoreflect.MessageKind && f.Desc.Cardinality() != protoreflect.Repeated && f.Message.GoIdent.GoName != "Timestamp"
}

func isLabelsField(f *protogen.Field) bool {
	return f.Desc.Kind() == protoreflect.MessageKind && f.Desc.Name() == "labels"
}

func isAnyField(f *protogen.Field) bool {
	return f.Desc.Kind() == protoreflect.MessageKind && f.Message.GoIdent.GoName == "Any"
}

func collectChildlen(parent *protogen.Message) ([]*protogen.Message, error) {
	var children []*protogen.Message
	for _, child := range parent.Messages {
		if child.Desc.IsMapEntry() {
			continue
		}
		children = append(children, child)

		xs, err := collectChildlen(child)
		if err != nil {
			return nil, err
		}
		children = append(children, xs...)
	}
	return children, nil
}

func generate(plugin *protogen.Plugin, input *protogen.File) error {
	var messages []*protogen.Message
	for _, m := range input.Messages {
		messages = append(messages, m)
		children, err := collectChildlen(m)
		if err != nil {
			return err
		}
		messages = append(messages, children...)
	}

	if len(messages) == 0 {
		// Don't generate a Go file, if that would be empty.
		return nil
	}

	file := plugin.NewGeneratedFile(input.GeneratedFilenamePrefix+"_fieldpath.pb.go", input.GoImportPath)
	file.P("// Code generated by protoc-gen-go-fieldpath. DO NOT EDIT.")
	file.P("// source: ", input.Desc.Path())
	file.P("package ", input.GoPackageName)

	gen := newGenerator(file)

	for _, m := range messages {
		gen.genFieldMethod(m)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Mender Software AS
//
//    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 main

import (
	"bytes"
	"errors"
	"io/ioutil"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

type stateTestController struct {
	fakeDevice
	fakeUpdater
	bootstrapErr  menderError
	imageID       string
	pollIntvl     time.Duration
	hasUpgrade    bool
	hasUpgradeErr menderError
	state         State
	updateResp    *UpdateResponse
	updateRespErr menderError
	authorize     menderError
}

func (s *stateTestController) Bootstrap() menderError {
	return s.bootstrapErr
}

func (s *stateTestController) GetCurrentImageID() string {
	return s.imageID
}

func (s *stateTestController) GetUpdatePollInterval() time.Duration {
	return s.pollIntvl
}

func (s *stateTestController) HasUpgrade() (bool, menderError) {
	return s.hasUpgrade, s.hasUpgradeErr
}

func (s *stateTestController) CheckUpdate() (*UpdateResponse, menderError) {
	return s.updateResp, s.updateRespErr
}

func (s *stateTestController) GetState() State {
	return s.state
}

func (s *stateTestController) SetState(state State) {
	s.state = state
}

func (s *stateTestController) Authorize() menderError {
	return s.authorize
}

func TestStateBase(t *testing.T) {
	bs := BaseState{
		MenderStateInit,
	}

	assert.Equal(t, MenderStateInit, bs.Id())
	assert.False(t, bs.Cancel())
}

func TestStateCancellable(t *testing.T) {
	cs := NewCancellableState(BaseState{
		id: MenderStateAuthorizeWait,
	})

	assert.Equal(t, MenderStateAuthorizeWait, cs.Id())

	var s State
	var c bool

	// no update
	var tstart, tend time.Time

	tstart = time.Now()
	s, c = cs.StateAfterWait(bootstrappedState, initState,
		100*time.Millisecond)
	tend = time.Now()
	// not cancelled should return the 'next' state
	assert.Equal(t, bootstrappedState, s)
	assert.False(t, c)
	assert.WithinDuration(t, tend, tstart, 105*time.Millisecond)

	// asynchronously cancel state operation
	go func() {
		c := cs.Cancel()
		assert.True(t, c)
	}()
	// should finish right away
	tstart = time.Now()
	s, c = cs.StateAfterWait(bootstrappedState, initState,
		100*time.Millisecond)
	tend = time.Now()
	// canceled should return the other state
	assert.Equal(t, initState, s)
	assert.True(t, c)
	assert.WithinDuration(t, tend, tstart, 5*time.Millisecond)

}

func TestStateError(t *testing.T) {

	fooerr := NewTransientError(errors.New("foo"))

	es := NewErrorState(fooerr)
	assert.Equal(t, MenderStateError, es.Id())
	assert.IsType(t, &ErrorState{}, es)
	errstate, _ := es.(*ErrorState)
	assert.NotNil(t, errstate)
	assert.Equal(t, fooerr, errstate.cause)

	es = NewErrorState(nil)
	errstate, _ = es.(*ErrorState)
	assert.NotNil(t, errstate)
	assert.Contains(t, errstate.cause.Error(), "general error")
}

func TestStateInit(t *testing.T) {
	i := InitState{}

	var s State
	var c bool
	s, c = i.Handle(&stateTestController{
		bootstrapErr: NewFatalError(errors.New("fake err")),
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	s, c = i.Handle(&stateTestController{})
	assert.IsType(t, &BootstrappedState{}, s)
	assert.False(t, c)
}

func TestStateBootstrapped(t *testing.T) {
	b := BootstrappedState{}

	var s State
	var c bool

	s, c = b.Handle(&stateTestController{})
	assert.IsType(t, &AuthorizedState{}, s)
	assert.False(t, c)

	s, c = b.Handle(&stateTestController{
		authorize: NewTransientError(errors.New("auth fail temp")),
	})
	assert.IsType(t, &AuthorizeWaitState{}, s)
	assert.False(t, c)

	s, c = b.Handle(&stateTestController{
		authorize: NewFatalError(errors.New("upgrade err")),
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)
}

func TestStateAuthorized(t *testing.T) {
	b := AuthorizedState{}

	var s State
	var c bool

	s, c = b.Handle(&stateTestController{
		hasUpgrade: false,
	})
	assert.IsType(t, &UpdateCheckWaitState{}, s)
	assert.False(t, c)

	s, c = b.Handle(&stateTestController{
		hasUpgrade: true,
	})
	assert.IsType(t, &UpdateCommitState{}, s)
	assert.False(t, c)

	s, c = b.Handle(&stateTestController{
		hasUpgradeErr: NewFatalError(errors.New("upgrade err")),
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)
}

func TestStateAuthorizeWait(t *testing.T) {
	cws := NewAuthorizeWaitState()

	var s State
	var c bool

	// no update
	var tstart, tend time.Time

	tstart = time.Now()
	s, c = cws.Handle(&stateTestController{
		pollIntvl: 100 * time.Millisecond,
	})
	tend = time.Now()
	assert.IsType(t, &BootstrappedState{}, s)
	assert.False(t, c)
	assert.WithinDuration(t, tend, tstart, 105*time.Millisecond)

	// asynchronously cancel state operation
	go func() {
		c := cws.Cancel()
		assert.True(t, c)
	}()
	// should finish right away
	tstart = time.Now()
	s, c = cws.Handle(&stateTestController{
		pollIntvl: 100 * time.Millisecond,
	})
	tend = time.Now()
	// canceled state should return itself
	assert.IsType(t, &AuthorizeWaitState{}, s)
	assert.True(t, c)
	assert.WithinDuration(t, tend, tstart, 5*time.Millisecond)
}

func TestStateUpdateCommit(t *testing.T) {
	cs := UpdateCommitState{}

	var s State
	var c bool

	// commit without errors
	s, c = cs.Handle(&stateTestController{})
	assert.IsType(t, &UpdateCheckWaitState{}, s)
	assert.False(t, c)

	s, c = cs.Handle(&stateTestController{
		fakeDevice: fakeDevice{
			retCommit: NewFatalError(errors.New("commit fail")),
		},
	})
	assert.IsType(t, s, &ErrorState{})
	assert.False(t, c)
}

func TestStateUpdateCheckWait(t *testing.T) {
	cws := NewUpdateCheckWaitState()

	var s State
	var c bool

	// no update
	var tstart, tend time.Time

	tstart = time.Now()
	s, c = cws.Handle(&stateTestController{
		pollIntvl: 100 * time.Millisecond,
	})
	tend = time.Now()
	assert.IsType(t, &UpdateCheckState{}, s)
	assert.False(t, c)
	assert.WithinDuration(t, tend, tstart, 105*time.Millisecond)

	// asynchronously cancel state operation
	go func() {
		c := cws.Cancel()
		assert.True(t, c)
	}()
	// should finish right away
	tstart = time.Now()
	s, c = cws.Handle(&stateTestController{
		pollIntvl: 100 * time.Millisecond,
	})
	tend = time.Now()
	// canceled state should return itself
	assert.IsType(t, &UpdateCheckWaitState{}, s)
	assert.True(t, c)
	assert.WithinDuration(t, tend, tstart, 5*time.Millisecond)
}

func TestStateUpdateCheck(t *testing.T) {
	cs := UpdateCheckState{}

	var s State
	var c bool

	// no update
	s, c = cs.Handle(&stateTestController{})
	assert.IsType(t, &UpdateCheckWaitState{}, s)
	assert.False(t, c)

	// pretend update check failed
	s, c = cs.Handle(&stateTestController{
		updateRespErr: NewTransientError(errors.New("check failed")),
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	// pretend we have an update
	update := &UpdateResponse{}

	s, c = cs.Handle(&stateTestController{
		updateResp: update,
	})
	assert.IsType(t, &UpdateFetchState{}, s)
	assert.False(t, c)
	ufs, _ := s.(*UpdateFetchState)
	assert.Equal(t, update, ufs.update)
}

func TestStateUpdateFetch(t *testing.T) {
	// pretend we have an update
	update := &UpdateResponse{}
	cs := NewUpdateFetchState(update)

	var s State
	var c bool

	// pretend update check failed
	s, c = cs.Handle(&stateTestController{
		fakeUpdater: fakeUpdater{
			fetchUpdateReturnError: NewTransientError(errors.New("fetch failed")),
		},
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	data := "test"
	stream := ioutil.NopCloser(bytes.NewBufferString(data))

	s, c = cs.Handle(&stateTestController{
		fakeUpdater: fakeUpdater{
			fetchUpdateReturnReadCloser: stream,
			fetchUpdateReturnSize:       int64(len(data)),
		},
	})
	assert.IsType(t, &UpdateInstallState{}, s)
	assert.False(t, c)
	uis, _ := s.(*UpdateInstallState)
	assert.Equal(t, stream, uis.imagein)
	assert.Equal(t, int64(len(data)), uis.size)
}

func TestStateUpdateInstall(t *testing.T) {
	data := "test"
	stream := ioutil.NopCloser(bytes.NewBufferString(data))

	uis := NewUpdateInstallState(stream, int64(len(data)))

	var s State
	var c bool

	// pretend update check failed
	s, c = uis.Handle(&stateTestController{
		fakeDevice: fakeDevice{
			retInstallUpdate: NewFatalError(errors.New("install failed")),
		},
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	s, c = uis.Handle(&stateTestController{
		fakeDevice: fakeDevice{
			retEnablePart: NewFatalError(errors.New("enable failed")),
		},
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	s, c = uis.Handle(&stateTestController{})
	assert.IsType(t, &RebootState{}, s)
	assert.False(t, c)
}

func TestStateReboot(t *testing.T) {
	rs := RebootState{}

	var s State
	var c bool

	s, c = rs.Handle(&stateTestController{
		fakeDevice: fakeDevice{
			retReboot: NewFatalError(errors.New("reboot failed")),
		}})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	s, c = rs.Handle(&stateTestController{})
	assert.IsType(t, &FinalState{}, s)
	assert.False(t, c)
}

func TestStateFinal(t *testing.T) {
	rs := FinalState{}

	assert.Panics(t, func() {
		rs.Handle(&stateTestController{})
	}, "final state Handle() should panic")
}
<commit_msg>state_test: update stateTestController helper<commit_after>// Copyright 2016 Mender Software AS
//
//    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 main

import (
	"bytes"
	"errors"
	"io"
	"io/ioutil"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

type stateTestController struct {
	fakeDevice
	updater       fakeUpdater
	bootstrapErr  menderError
	imageID       string
	pollIntvl     time.Duration
	hasUpgrade    bool
	hasUpgradeErr menderError
	state         State
	updateResp    *UpdateResponse
	updateRespErr menderError
	authorize     menderError
}

func (s *stateTestController) Bootstrap() menderError {
	return s.bootstrapErr
}

func (s *stateTestController) GetCurrentImageID() string {
	return s.imageID
}

func (s *stateTestController) GetUpdatePollInterval() time.Duration {
	return s.pollIntvl
}

func (s *stateTestController) HasUpgrade() (bool, menderError) {
	return s.hasUpgrade, s.hasUpgradeErr
}

func (s *stateTestController) CheckUpdate() (*UpdateResponse, menderError) {
	return s.updateResp, s.updateRespErr
}

func (s *stateTestController) FetchUpdate(url string) (io.ReadCloser, int64, error) {
	return s.updater.FetchUpdate(nil, url)
}

func (s *stateTestController) GetState() State {
	return s.state
}

func (s *stateTestController) SetState(state State) {
	s.state = state
}

func (s *stateTestController) Authorize() menderError {
	return s.authorize
}

func TestStateBase(t *testing.T) {
	bs := BaseState{
		MenderStateInit,
	}

	assert.Equal(t, MenderStateInit, bs.Id())
	assert.False(t, bs.Cancel())
}

func TestStateCancellable(t *testing.T) {
	cs := NewCancellableState(BaseState{
		id: MenderStateAuthorizeWait,
	})

	assert.Equal(t, MenderStateAuthorizeWait, cs.Id())

	var s State
	var c bool

	// no update
	var tstart, tend time.Time

	tstart = time.Now()
	s, c = cs.StateAfterWait(bootstrappedState, initState,
		100*time.Millisecond)
	tend = time.Now()
	// not cancelled should return the 'next' state
	assert.Equal(t, bootstrappedState, s)
	assert.False(t, c)
	assert.WithinDuration(t, tend, tstart, 105*time.Millisecond)

	// asynchronously cancel state operation
	go func() {
		c := cs.Cancel()
		assert.True(t, c)
	}()
	// should finish right away
	tstart = time.Now()
	s, c = cs.StateAfterWait(bootstrappedState, initState,
		100*time.Millisecond)
	tend = time.Now()
	// canceled should return the other state
	assert.Equal(t, initState, s)
	assert.True(t, c)
	assert.WithinDuration(t, tend, tstart, 5*time.Millisecond)

}

func TestStateError(t *testing.T) {

	fooerr := NewTransientError(errors.New("foo"))

	es := NewErrorState(fooerr)
	assert.Equal(t, MenderStateError, es.Id())
	assert.IsType(t, &ErrorState{}, es)
	errstate, _ := es.(*ErrorState)
	assert.NotNil(t, errstate)
	assert.Equal(t, fooerr, errstate.cause)

	es = NewErrorState(nil)
	errstate, _ = es.(*ErrorState)
	assert.NotNil(t, errstate)
	assert.Contains(t, errstate.cause.Error(), "general error")
}

func TestStateInit(t *testing.T) {
	i := InitState{}

	var s State
	var c bool
	s, c = i.Handle(&stateTestController{
		bootstrapErr: NewFatalError(errors.New("fake err")),
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	s, c = i.Handle(&stateTestController{})
	assert.IsType(t, &BootstrappedState{}, s)
	assert.False(t, c)
}

func TestStateBootstrapped(t *testing.T) {
	b := BootstrappedState{}

	var s State
	var c bool

	s, c = b.Handle(&stateTestController{})
	assert.IsType(t, &AuthorizedState{}, s)
	assert.False(t, c)

	s, c = b.Handle(&stateTestController{
		authorize: NewTransientError(errors.New("auth fail temp")),
	})
	assert.IsType(t, &AuthorizeWaitState{}, s)
	assert.False(t, c)

	s, c = b.Handle(&stateTestController{
		authorize: NewFatalError(errors.New("upgrade err")),
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)
}

func TestStateAuthorized(t *testing.T) {
	b := AuthorizedState{}

	var s State
	var c bool

	s, c = b.Handle(&stateTestController{
		hasUpgrade: false,
	})
	assert.IsType(t, &UpdateCheckWaitState{}, s)
	assert.False(t, c)

	s, c = b.Handle(&stateTestController{
		hasUpgrade: true,
	})
	assert.IsType(t, &UpdateCommitState{}, s)
	assert.False(t, c)

	s, c = b.Handle(&stateTestController{
		hasUpgradeErr: NewFatalError(errors.New("upgrade err")),
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)
}

func TestStateAuthorizeWait(t *testing.T) {
	cws := NewAuthorizeWaitState()

	var s State
	var c bool

	// no update
	var tstart, tend time.Time

	tstart = time.Now()
	s, c = cws.Handle(&stateTestController{
		pollIntvl: 100 * time.Millisecond,
	})
	tend = time.Now()
	assert.IsType(t, &BootstrappedState{}, s)
	assert.False(t, c)
	assert.WithinDuration(t, tend, tstart, 105*time.Millisecond)

	// asynchronously cancel state operation
	go func() {
		c := cws.Cancel()
		assert.True(t, c)
	}()
	// should finish right away
	tstart = time.Now()
	s, c = cws.Handle(&stateTestController{
		pollIntvl: 100 * time.Millisecond,
	})
	tend = time.Now()
	// canceled state should return itself
	assert.IsType(t, &AuthorizeWaitState{}, s)
	assert.True(t, c)
	assert.WithinDuration(t, tend, tstart, 5*time.Millisecond)
}

func TestStateUpdateCommit(t *testing.T) {
	cs := UpdateCommitState{}

	var s State
	var c bool

	// commit without errors
	s, c = cs.Handle(&stateTestController{})
	assert.IsType(t, &UpdateCheckWaitState{}, s)
	assert.False(t, c)

	s, c = cs.Handle(&stateTestController{
		fakeDevice: fakeDevice{
			retCommit: NewFatalError(errors.New("commit fail")),
		},
	})
	assert.IsType(t, s, &ErrorState{})
	assert.False(t, c)
}

func TestStateUpdateCheckWait(t *testing.T) {
	cws := NewUpdateCheckWaitState()

	var s State
	var c bool

	// no update
	var tstart, tend time.Time

	tstart = time.Now()
	s, c = cws.Handle(&stateTestController{
		pollIntvl: 100 * time.Millisecond,
	})
	tend = time.Now()
	assert.IsType(t, &UpdateCheckState{}, s)
	assert.False(t, c)
	assert.WithinDuration(t, tend, tstart, 105*time.Millisecond)

	// asynchronously cancel state operation
	go func() {
		c := cws.Cancel()
		assert.True(t, c)
	}()
	// should finish right away
	tstart = time.Now()
	s, c = cws.Handle(&stateTestController{
		pollIntvl: 100 * time.Millisecond,
	})
	tend = time.Now()
	// canceled state should return itself
	assert.IsType(t, &UpdateCheckWaitState{}, s)
	assert.True(t, c)
	assert.WithinDuration(t, tend, tstart, 5*time.Millisecond)
}

func TestStateUpdateCheck(t *testing.T) {
	cs := UpdateCheckState{}

	var s State
	var c bool

	// no update
	s, c = cs.Handle(&stateTestController{})
	assert.IsType(t, &UpdateCheckWaitState{}, s)
	assert.False(t, c)

	// pretend update check failed
	s, c = cs.Handle(&stateTestController{
		updateRespErr: NewTransientError(errors.New("check failed")),
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	// pretend we have an update
	update := &UpdateResponse{}

	s, c = cs.Handle(&stateTestController{
		updateResp: update,
	})
	assert.IsType(t, &UpdateFetchState{}, s)
	assert.False(t, c)
	ufs, _ := s.(*UpdateFetchState)
	assert.Equal(t, update, ufs.update)
}

func TestStateUpdateFetch(t *testing.T) {
	// pretend we have an update
	update := &UpdateResponse{}
	cs := NewUpdateFetchState(update)

	var s State
	var c bool

	// pretend update check failed
	s, c = cs.Handle(&stateTestController{
		updater: fakeUpdater{
			fetchUpdateReturnError: NewTransientError(errors.New("fetch failed")),
		},
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	data := "test"
	stream := ioutil.NopCloser(bytes.NewBufferString(data))

	s, c = cs.Handle(&stateTestController{
		updater: fakeUpdater{
			fetchUpdateReturnReadCloser: stream,
			fetchUpdateReturnSize:       int64(len(data)),
		},
	})
	assert.IsType(t, &UpdateInstallState{}, s)
	assert.False(t, c)
	uis, _ := s.(*UpdateInstallState)
	assert.Equal(t, stream, uis.imagein)
	assert.Equal(t, int64(len(data)), uis.size)
}

func TestStateUpdateInstall(t *testing.T) {
	data := "test"
	stream := ioutil.NopCloser(bytes.NewBufferString(data))

	uis := NewUpdateInstallState(stream, int64(len(data)))

	var s State
	var c bool

	// pretend update check failed
	s, c = uis.Handle(&stateTestController{
		fakeDevice: fakeDevice{
			retInstallUpdate: NewFatalError(errors.New("install failed")),
		},
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	s, c = uis.Handle(&stateTestController{
		fakeDevice: fakeDevice{
			retEnablePart: NewFatalError(errors.New("enable failed")),
		},
	})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	s, c = uis.Handle(&stateTestController{})
	assert.IsType(t, &RebootState{}, s)
	assert.False(t, c)
}

func TestStateReboot(t *testing.T) {
	rs := RebootState{}

	var s State
	var c bool

	s, c = rs.Handle(&stateTestController{
		fakeDevice: fakeDevice{
			retReboot: NewFatalError(errors.New("reboot failed")),
		}})
	assert.IsType(t, &ErrorState{}, s)
	assert.False(t, c)

	s, c = rs.Handle(&stateTestController{})
	assert.IsType(t, &FinalState{}, s)
	assert.False(t, c)
}

func TestStateFinal(t *testing.T) {
	rs := FinalState{}

	assert.Panics(t, func() {
		rs.Handle(&stateTestController{})
	}, "final state Handle() should panic")
}
<|endoftext|>


<commit_before>// Copyright 2016 The rkt 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.

//+build linux

package v1

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"

	"github.com/coreos/rkt/pkg/fs"
	"github.com/hashicorp/errwrap"
)

// mountFsRO remounts the given mountPoint using the given flags read-only.
func mountFsRO(m fs.Mounter, mountPoint string, flags uintptr) error {
	flags = flags |
		syscall.MS_BIND |
		syscall.MS_REMOUNT |
		syscall.MS_RDONLY

	if err := m.Mount(mountPoint, mountPoint, "", flags, ""); err != nil {
		return errwrap.Wrap(fmt.Errorf("error remounting read-only %q", mountPoint), err)
	}

	return nil
}

func parseCgroups(f io.Reader) (map[int][]string, error) {
	sc := bufio.NewScanner(f)

	// skip first line since it is a comment
	sc.Scan()

	cgroups := make(map[int][]string)
	for sc.Scan() {
		var controller string
		var hierarchy int
		var num int
		var enabled int
		fmt.Sscanf(sc.Text(), "%s %d %d %d", &controller, &hierarchy, &num, &enabled)

		if enabled == 1 {
			if _, ok := cgroups[hierarchy]; !ok {
				cgroups[hierarchy] = []string{controller}
			} else {
				cgroups[hierarchy] = append(cgroups[hierarchy], controller)
			}
		}
	}

	if err := sc.Err(); err != nil {
		return nil, err
	}

	return cgroups, nil
}

// GetEnabledCgroups returns a map with the enabled cgroup controllers grouped by
// hierarchy
func GetEnabledCgroups() (map[int][]string, error) {
	cgroupsFile, err := os.Open("/proc/cgroups")
	if err != nil {
		return nil, err
	}
	defer cgroupsFile.Close()

	cgroups, err := parseCgroups(cgroupsFile)
	if err != nil {
		return nil, errwrap.Wrap(errors.New("error parsing /proc/cgroups"), err)
	}

	return cgroups, nil
}

// GetControllerDirs takes a map with the enabled cgroup controllers grouped by
// hierarchy and returns the directory names as they should be in
// /sys/fs/cgroup
func GetControllerDirs(cgroups map[int][]string) []string {
	var controllers []string
	for _, cs := range cgroups {
		controllers = append(controllers, strings.Join(cs, ","))
	}

	return controllers
}

func getControllerSymlinks(cgroups map[int][]string) map[string]string {
	symlinks := make(map[string]string)

	for _, cs := range cgroups {
		if len(cs) > 1 {
			tgt := strings.Join(cs, ",")
			for _, ln := range cs {
				symlinks[ln] = tgt
			}
		}
	}

	return symlinks
}

func parseCgroupController(cgroupPath, controller string) ([]string, error) {
	cg, err := os.Open(cgroupPath)
	if err != nil {
		return nil, errwrap.Wrap(errors.New("error opening /proc/self/cgroup"), err)
	}
	defer cg.Close()

	s := bufio.NewScanner(cg)
	for s.Scan() {
		parts := strings.SplitN(s.Text(), ":", 3)
		if len(parts) < 3 {
			return nil, fmt.Errorf("error parsing /proc/self/cgroup")
		}
		controllerParts := strings.Split(parts[1], ",")
		for _, c := range controllerParts {
			if c == controller {
				return parts, nil
			}
		}
	}

	return nil, fmt.Errorf("controller %q not found", controller)
}

// GetOwnCgroupPath returns the cgroup path of this process in controller
// hierarchy
func GetOwnCgroupPath(controller string) (string, error) {
	parts, err := parseCgroupController("/proc/self/cgroup", controller)
	if err != nil {
		return "", err
	}
	return parts[2], nil
}

// GetCgroupPathByPid returns the cgroup path of the process with the given pid
// and given controller.
func GetCgroupPathByPid(pid int, controller string) (string, error) {
	parts, err := parseCgroupController(fmt.Sprintf("/proc/%d/cgroup", pid), controller)
	if err != nil {
		return "", err
	}
	return parts[2], nil
}

// JoinSubcgroup makes the calling process join the subcgroup hierarchy on a
// particular controller
func JoinSubcgroup(controller string, subcgroup string) error {
	subcgroupPath := filepath.Join("/sys/fs/cgroup", controller, subcgroup)
	if err := os.MkdirAll(subcgroupPath, 0600); err != nil {
		return errwrap.Wrap(fmt.Errorf("error creating %q subcgroup", subcgroup), err)
	}
	pidBytes := []byte(strconv.Itoa(os.Getpid()))
	if err := ioutil.WriteFile(filepath.Join(subcgroupPath, "cgroup.procs"), pidBytes, 0600); err != nil {
		return errwrap.Wrap(fmt.Errorf("error adding ourselves to the %q subcgroup", subcgroup), err)
	}

	return nil
}

// Ensure that the hierarchy has consistent cpu restrictions.
// This may fail; since this is "fixup" code, we should ignore
// the error and proceed.
//
// This was originally a workaround for https://github.com/coreos/rkt/issues/1210
// but is actually useful to have around
//
// cpuSetPath should be <stage1rootfs>/sys/fs/cgroup/cpuset
func fixCpusetKnobs(cpusetPath, subcgroup, knob string) error {
	if err := os.MkdirAll(filepath.Join(cpusetPath, subcgroup), 0755); err != nil {
		return err
	}

	dirs := filepath.SplitList(subcgroup)

	// Loop over every entry in the hierarchy, putting in the parent's value
	// unless there is one already there.
	// Read from the root knob
	parentFile := filepath.Join(cpusetPath, knob)
	parentData, err := ioutil.ReadFile(parentFile)
	if err != nil {
		return errwrap.Wrapf("error reading cgroup "+parentFile, err)
	}

	// Loop over every directory in the subcgroup path
	currDir := cpusetPath
	for _, dir := range dirs {
		currDir = filepath.Join(currDir, dir)

		childFile := filepath.Join(currDir, knob)
		childData, err := ioutil.ReadFile(childFile)
		if err != nil {
			return errwrap.Wrapf("error reading cgroup "+childFile, err)
		}

		// If there is already a value, don't write - and propagate
		// this value to subsequent children
		if strings.TrimSpace(string(childData)) != "" {
			parentData = childData
			continue
		}

		// Workaround: just write twice to workaround the kernel bug fixed by this commit:
		// https://github.com/torvalds/linux/commit/24ee3cf89bef04e8bc23788aca4e029a3f0f06d9
		if err := ioutil.WriteFile(childFile, parentData, 0644); err != nil {
			return errwrap.Wrapf("error writing cgroup "+childFile, err)
		}
		if err := ioutil.WriteFile(childFile, parentData, 0644); err != nil {
			return errwrap.Wrapf("error writing cgroup "+childFile, err)
		}
	}
	return nil
}

// IsControllerMounted returns whether a controller is mounted by checking that
// cgroup.procs is accessible
func IsControllerMounted(c string) (bool, error) {
	cgroupProcsPath := filepath.Join("/sys/fs/cgroup", c, "cgroup.procs")
	if _, err := os.Stat(cgroupProcsPath); err != nil {
		if !os.IsNotExist(err) {
			return false, err
		}
		return false, nil
	}

	return true, nil
}

// CreateCgroups mounts the v1 cgroup controllers hierarchy in /sys/fs/cgroup
// under root
func CreateCgroups(m fs.Mounter, root string, enabledCgroups map[int][]string, mountContext string) error {
	controllers := GetControllerDirs(enabledCgroups)

	sys := filepath.Join(root, "/sys")
	if err := os.MkdirAll(sys, 0700); err != nil {
		return err
	}

	var sysfsFlags uintptr = syscall.MS_NOSUID |
		syscall.MS_NOEXEC |
		syscall.MS_NODEV

	// If we're mounting the host cgroups, /sys is probably mounted so we
	// ignore EBUSY
	if err := m.Mount("sysfs", sys, "sysfs", sysfsFlags, ""); err != nil && err != syscall.EBUSY {
		return errwrap.Wrap(fmt.Errorf("error mounting %q", sys), err)
	}

	cgroupTmpfs := filepath.Join(root, "/sys/fs/cgroup")
	if err := os.MkdirAll(cgroupTmpfs, 0700); err != nil {
		return err
	}

	var cgroupTmpfsFlags uintptr = syscall.MS_NOSUID |
		syscall.MS_NOEXEC |
		syscall.MS_NODEV |
		syscall.MS_STRICTATIME

	options := "mode=755"
	if mountContext != "" {
		options = fmt.Sprintf("mode=755,context=\"%s\"", mountContext)
	}

	if err := m.Mount("tmpfs", cgroupTmpfs, "tmpfs", cgroupTmpfsFlags, options); err != nil {
		return errwrap.Wrap(fmt.Errorf("error mounting %q", cgroupTmpfs), err)
	}

	// Mount controllers
	for _, c := range controllers {
		cPath := filepath.Join(root, "/sys/fs/cgroup", c)
		if err := os.MkdirAll(cPath, 0700); err != nil {
			return err
		}

		var flags uintptr = syscall.MS_NOSUID |
			syscall.MS_NOEXEC |
			syscall.MS_NODEV

		if err := m.Mount("cgroup", cPath, "cgroup", flags, c); err != nil {
			return errwrap.Wrap(fmt.Errorf("error mounting %q", cPath), err)
		}
	}

	// Create symlinks for combined controllers
	symlinks := getControllerSymlinks(enabledCgroups)
	for ln, tgt := range symlinks {
		lnPath := filepath.Join(cgroupTmpfs, ln)
		if err := os.Symlink(tgt, lnPath); err != nil {
			return errwrap.Wrap(errors.New("error creating symlink"), err)
		}
	}

	systemdControllerPath := filepath.Join(root, "/sys/fs/cgroup/systemd")
	if err := os.MkdirAll(systemdControllerPath, 0700); err != nil {
		return err
	}

	// Bind-mount cgroup tmpfs filesystem read-only
	return mountFsRO(m, cgroupTmpfs, cgroupTmpfsFlags)
}

// RemountCgroups remounts the v1 cgroup hierarchy under root.
// It mounts /sys/fs/cgroup/[controller] read-only,
// but leaves needed knobs in the pod's subcgroup read-write,
// such that systemd inside stage1 can apply isolators to them.
// It leaves /sys read-write if the given readWrite parameter is true.
// When this is done, <stage1>/sys/fs/cgroup/<controller> should be RO, and
// <stage1>/sys/fs/cgroup/<cotroller>/.../machine-rkt/.../system.slice should be RW
func RemountCgroups(m fs.Mounter, root string, enabledCgroups map[int][]string, subcgroup string, readWrite bool) error {
	controllers := GetControllerDirs(enabledCgroups)
	cgroupTmpfs := filepath.Join(root, "/sys/fs/cgroup")
	sysPath := filepath.Join(root, "/sys")

	var flags uintptr = syscall.MS_NOSUID |
		syscall.MS_NOEXEC |
		syscall.MS_NODEV

	// Mount RW the controllers for this pod
	for _, c := range controllers {
		cPath := filepath.Join(cgroupTmpfs, c)
		subcgroupPath := filepath.Join(cPath, subcgroup, "system.slice")

		if err := os.MkdirAll(subcgroupPath, 0755); err != nil {
			return err
		}
		if err := m.Mount(subcgroupPath, subcgroupPath, "", syscall.MS_BIND, ""); err != nil {
			return errwrap.Wrap(fmt.Errorf("error bind mounting %q", subcgroupPath), err)
		}

		// Workaround for https://github.com/coreos/rkt/issues/1210
		// It is OK to ignore errors here.
		if c == "cpuset" {
			_ = fixCpusetKnobs(cPath, subcgroup, "cpuset.mems")
			_ = fixCpusetKnobs(cPath, subcgroup, "cpuset.cpus")
		}

		// Re-mount controller read-only to prevent the container modifying host controllers
		if err := mountFsRO(m, cPath, flags); err != nil {
			return err
		}
	}

	if readWrite { // leave sys r/w?
		return nil
	}

	// Bind-mount sys filesystem read-only
	return mountFsRO(m, sysPath, flags)
}
<commit_msg>stage1: fix incorrect splitting function<commit_after>// Copyright 2016 The rkt 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.

//+build linux

package v1

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"

	"github.com/coreos/rkt/pkg/fs"
	"github.com/hashicorp/errwrap"
)

// mountFsRO remounts the given mountPoint using the given flags read-only.
func mountFsRO(m fs.Mounter, mountPoint string, flags uintptr) error {
	flags = flags |
		syscall.MS_BIND |
		syscall.MS_REMOUNT |
		syscall.MS_RDONLY

	if err := m.Mount(mountPoint, mountPoint, "", flags, ""); err != nil {
		return errwrap.Wrap(fmt.Errorf("error remounting read-only %q", mountPoint), err)
	}

	return nil
}

func parseCgroups(f io.Reader) (map[int][]string, error) {
	sc := bufio.NewScanner(f)

	// skip first line since it is a comment
	sc.Scan()

	cgroups := make(map[int][]string)
	for sc.Scan() {
		var controller string
		var hierarchy int
		var num int
		var enabled int
		fmt.Sscanf(sc.Text(), "%s %d %d %d", &controller, &hierarchy, &num, &enabled)

		if enabled == 1 {
			if _, ok := cgroups[hierarchy]; !ok {
				cgroups[hierarchy] = []string{controller}
			} else {
				cgroups[hierarchy] = append(cgroups[hierarchy], controller)
			}
		}
	}

	if err := sc.Err(); err != nil {
		return nil, err
	}

	return cgroups, nil
}

// GetEnabledCgroups returns a map with the enabled cgroup controllers grouped by
// hierarchy
func GetEnabledCgroups() (map[int][]string, error) {
	cgroupsFile, err := os.Open("/proc/cgroups")
	if err != nil {
		return nil, err
	}
	defer cgroupsFile.Close()

	cgroups, err := parseCgroups(cgroupsFile)
	if err != nil {
		return nil, errwrap.Wrap(errors.New("error parsing /proc/cgroups"), err)
	}

	return cgroups, nil
}

// GetControllerDirs takes a map with the enabled cgroup controllers grouped by
// hierarchy and returns the directory names as they should be in
// /sys/fs/cgroup
func GetControllerDirs(cgroups map[int][]string) []string {
	var controllers []string
	for _, cs := range cgroups {
		controllers = append(controllers, strings.Join(cs, ","))
	}

	return controllers
}

func getControllerSymlinks(cgroups map[int][]string) map[string]string {
	symlinks := make(map[string]string)

	for _, cs := range cgroups {
		if len(cs) > 1 {
			tgt := strings.Join(cs, ",")
			for _, ln := range cs {
				symlinks[ln] = tgt
			}
		}
	}

	return symlinks
}

func parseCgroupController(cgroupPath, controller string) ([]string, error) {
	cg, err := os.Open(cgroupPath)
	if err != nil {
		return nil, errwrap.Wrap(errors.New("error opening /proc/self/cgroup"), err)
	}
	defer cg.Close()

	s := bufio.NewScanner(cg)
	for s.Scan() {
		parts := strings.SplitN(s.Text(), ":", 3)
		if len(parts) < 3 {
			return nil, fmt.Errorf("error parsing /proc/self/cgroup")
		}
		controllerParts := strings.Split(parts[1], ",")
		for _, c := range controllerParts {
			if c == controller {
				return parts, nil
			}
		}
	}

	return nil, fmt.Errorf("controller %q not found", controller)
}

// GetOwnCgroupPath returns the cgroup path of this process in controller
// hierarchy
func GetOwnCgroupPath(controller string) (string, error) {
	parts, err := parseCgroupController("/proc/self/cgroup", controller)
	if err != nil {
		return "", err
	}
	return parts[2], nil
}

// GetCgroupPathByPid returns the cgroup path of the process with the given pid
// and given controller.
func GetCgroupPathByPid(pid int, controller string) (string, error) {
	parts, err := parseCgroupController(fmt.Sprintf("/proc/%d/cgroup", pid), controller)
	if err != nil {
		return "", err
	}
	return parts[2], nil
}

// JoinSubcgroup makes the calling process join the subcgroup hierarchy on a
// particular controller
func JoinSubcgroup(controller string, subcgroup string) error {
	subcgroupPath := filepath.Join("/sys/fs/cgroup", controller, subcgroup)
	if err := os.MkdirAll(subcgroupPath, 0600); err != nil {
		return errwrap.Wrap(fmt.Errorf("error creating %q subcgroup", subcgroup), err)
	}
	pidBytes := []byte(strconv.Itoa(os.Getpid()))
	if err := ioutil.WriteFile(filepath.Join(subcgroupPath, "cgroup.procs"), pidBytes, 0600); err != nil {
		return errwrap.Wrap(fmt.Errorf("error adding ourselves to the %q subcgroup", subcgroup), err)
	}

	return nil
}

// Ensure that the hierarchy has consistent cpu restrictions.
// This may fail; since this is "fixup" code, we should ignore
// the error and proceed.
//
// This was originally a workaround for https://github.com/coreos/rkt/issues/1210
// but is actually useful to have around
//
// cpuSetPath should be <stage1rootfs>/sys/fs/cgroup/cpuset
func fixCpusetKnobs(cpusetPath, subcgroup, knob string) error {
	if err := os.MkdirAll(filepath.Join(cpusetPath, subcgroup), 0755); err != nil {
		return err
	}

	dirs := strings.Split(subcgroup, "/")

	// Loop over every entry in the hierarchy, putting in the parent's value
	// unless there is one already there.
	// Read from the root knob
	parentFile := filepath.Join(cpusetPath, knob)
	parentData, err := ioutil.ReadFile(parentFile)
	if err != nil {
		return errwrap.Wrapf("error reading cgroup "+parentFile, err)
	}

	// Loop over every directory in the subcgroup path
	currDir := cpusetPath
	for _, dir := range dirs {
		currDir = filepath.Join(currDir, dir)

		childFile := filepath.Join(currDir, knob)
		childData, err := ioutil.ReadFile(childFile)
		if err != nil {
			return errwrap.Wrapf("error reading cgroup "+childFile, err)
		}

		// If there is already a value, don't write - and propagate
		// this value to subsequent children
		if strings.TrimSpace(string(childData)) != "" {
			parentData = childData
			continue
		}

		// Workaround: just write twice to workaround the kernel bug fixed by this commit:
		// https://github.com/torvalds/linux/commit/24ee3cf89bef04e8bc23788aca4e029a3f0f06d9
		if err := ioutil.WriteFile(childFile, parentData, 0644); err != nil {
			return errwrap.Wrapf("error writing cgroup "+childFile, err)
		}
		if err := ioutil.WriteFile(childFile, parentData, 0644); err != nil {
			return errwrap.Wrapf("error writing cgroup "+childFile, err)
		}
	}
	return nil
}

// IsControllerMounted returns whether a controller is mounted by checking that
// cgroup.procs is accessible
func IsControllerMounted(c string) (bool, error) {
	cgroupProcsPath := filepath.Join("/sys/fs/cgroup", c, "cgroup.procs")
	if _, err := os.Stat(cgroupProcsPath); err != nil {
		if !os.IsNotExist(err) {
			return false, err
		}
		return false, nil
	}

	return true, nil
}

// CreateCgroups mounts the v1 cgroup controllers hierarchy in /sys/fs/cgroup
// under root
func CreateCgroups(m fs.Mounter, root string, enabledCgroups map[int][]string, mountContext string) error {
	controllers := GetControllerDirs(enabledCgroups)

	sys := filepath.Join(root, "/sys")
	if err := os.MkdirAll(sys, 0700); err != nil {
		return err
	}

	var sysfsFlags uintptr = syscall.MS_NOSUID |
		syscall.MS_NOEXEC |
		syscall.MS_NODEV

	// If we're mounting the host cgroups, /sys is probably mounted so we
	// ignore EBUSY
	if err := m.Mount("sysfs", sys, "sysfs", sysfsFlags, ""); err != nil && err != syscall.EBUSY {
		return errwrap.Wrap(fmt.Errorf("error mounting %q", sys), err)
	}

	cgroupTmpfs := filepath.Join(root, "/sys/fs/cgroup")
	if err := os.MkdirAll(cgroupTmpfs, 0700); err != nil {
		return err
	}

	var cgroupTmpfsFlags uintptr = syscall.MS_NOSUID |
		syscall.MS_NOEXEC |
		syscall.MS_NODEV |
		syscall.MS_STRICTATIME

	options := "mode=755"
	if mountContext != "" {
		options = fmt.Sprintf("mode=755,context=\"%s\"", mountContext)
	}

	if err := m.Mount("tmpfs", cgroupTmpfs, "tmpfs", cgroupTmpfsFlags, options); err != nil {
		return errwrap.Wrap(fmt.Errorf("error mounting %q", cgroupTmpfs), err)
	}

	// Mount controllers
	for _, c := range controllers {
		cPath := filepath.Join(root, "/sys/fs/cgroup", c)
		if err := os.MkdirAll(cPath, 0700); err != nil {
			return err
		}

		var flags uintptr = syscall.MS_NOSUID |
			syscall.MS_NOEXEC |
			syscall.MS_NODEV

		if err := m.Mount("cgroup", cPath, "cgroup", flags, c); err != nil {
			return errwrap.Wrap(fmt.Errorf("error mounting %q", cPath), err)
		}
	}

	// Create symlinks for combined controllers
	symlinks := getControllerSymlinks(enabledCgroups)
	for ln, tgt := range symlinks {
		lnPath := filepath.Join(cgroupTmpfs, ln)
		if err := os.Symlink(tgt, lnPath); err != nil {
			return errwrap.Wrap(errors.New("error creating symlink"), err)
		}
	}

	systemdControllerPath := filepath.Join(root, "/sys/fs/cgroup/systemd")
	if err := os.MkdirAll(systemdControllerPath, 0700); err != nil {
		return err
	}

	// Bind-mount cgroup tmpfs filesystem read-only
	return mountFsRO(m, cgroupTmpfs, cgroupTmpfsFlags)
}

// RemountCgroups remounts the v1 cgroup hierarchy under root.
// It mounts /sys/fs/cgroup/[controller] read-only,
// but leaves needed knobs in the pod's subcgroup read-write,
// such that systemd inside stage1 can apply isolators to them.
// It leaves /sys read-write if the given readWrite parameter is true.
// When this is done, <stage1>/sys/fs/cgroup/<controller> should be RO, and
// <stage1>/sys/fs/cgroup/<cotroller>/.../machine-rkt/.../system.slice should be RW
func RemountCgroups(m fs.Mounter, root string, enabledCgroups map[int][]string, subcgroup string, readWrite bool) error {
	controllers := GetControllerDirs(enabledCgroups)
	cgroupTmpfs := filepath.Join(root, "/sys/fs/cgroup")
	sysPath := filepath.Join(root, "/sys")

	var flags uintptr = syscall.MS_NOSUID |
		syscall.MS_NOEXEC |
		syscall.MS_NODEV

	// Mount RW the controllers for this pod
	for _, c := range controllers {
		cPath := filepath.Join(cgroupTmpfs, c)
		subcgroupPath := filepath.Join(cPath, subcgroup, "system.slice")

		if err := os.MkdirAll(subcgroupPath, 0755); err != nil {
			return err
		}
		if err := m.Mount(subcgroupPath, subcgroupPath, "", syscall.MS_BIND, ""); err != nil {
			return errwrap.Wrap(fmt.Errorf("error bind mounting %q", subcgroupPath), err)
		}

		// Workaround for https://github.com/coreos/rkt/issues/1210
		// It is OK to ignore errors here.
		if c == "cpuset" {
			_ = fixCpusetKnobs(cPath, subcgroup, "cpuset.mems")
			_ = fixCpusetKnobs(cPath, subcgroup, "cpuset.cpus")
		}

		// Re-mount controller read-only to prevent the container modifying host controllers
		if err := mountFsRO(m, cPath, flags); err != nil {
			return err
		}
	}

	if readWrite { // leave sys r/w?
		return nil
	}

	// Bind-mount sys filesystem read-only
	return mountFsRO(m, sysPath, flags)
}
<|endoftext|>


<commit_before>package trackello

import (
	"fmt"

	"github.com/VojtechVitek/go-trello"
	"github.com/fatih/color"
)

// statistics provides a way to show statistical information about a list, card or whatnot by aggregating the updates,
// comments, checklists, and other actions under a specific item.
type statistics struct {
	comments, // represented by a horizontal ellepsis ⋯ 0x22EF
	updates, // represented by a keyboard 0x2328
	creates, // represented by plus +
	checkListItemUpdates int // represented by check mark ✓ 0x2713
}

// AddCalculation will the new action to the Card's statistics.
func (c *Card) AddCalculation(a trello.Action) {
	switch a.Type {
	case "commentCard":
		c.stats.comments++
	case "updateCard":
		c.stats.updates++
	case "createCard", "addChecklistToCard", "addAttachmentToCard":
		c.stats.creates++
	case "updateCheckItemStateOnCard":
		c.stats.checkListItemUpdates++
	default:
		fmt.Printf("Unmapped action type: %s.  Defaulting to update\n", a.Type)
		c.stats.updates++
	}
}

// PrintStatistics will print the statistics information out.
// Example format: [ 3 +  2 ≡  0 ✓  1 … ]
func (s *statistics) PrintStatistics() string {
	if s == nil {
		s = &statistics{}
	}
	stats := "[" + color.CyanString("%d +", s.updates)
	stats = stats + color.RedString(" %d ≡", s.comments)
	stats = stats + color.GreenString(" %d ✓", s.checkListItemUpdates)
	stats = stats + color.MagentaString(" %d …", s.creates)
	stats = stats + "]"
	return stats
}
<commit_msg>add a missing trello action<commit_after>package trackello

import (
	"fmt"

	"github.com/VojtechVitek/go-trello"
	"github.com/fatih/color"
)

// statistics provides a way to show statistical information about a list, card or whatnot by aggregating the updates,
// comments, checklists, and other actions under a specific item.
type statistics struct {
	comments, // represented by a horizontal ellepsis ⋯ 0x22EF
	updates, // represented by a keyboard 0x2328
	creates, // represented by plus +
	checkListItemUpdates int // represented by check mark ✓ 0x2713
}

// AddCalculation will the new action to the Card's statistics.
func (c *Card) AddCalculation(a trello.Action) {
	switch a.Type {
	case "commentCard":
		c.stats.comments++
	case "updateCard", "deleteAttachmentFromCard":
		c.stats.updates++
	case "createCard", "addChecklistToCard", "addAttachmentToCard":
		c.stats.creates++
	case "updateCheckItemStateOnCard":
		c.stats.checkListItemUpdates++
	default:
		fmt.Printf("Unmapped action type: %s.  Defaulting to update\n", a.Type)
		c.stats.updates++
	}
}

// PrintStatistics will print the statistics information out.
// Example format: [ 3 +  2 ≡  0 ✓  1 … ]
func (s *statistics) PrintStatistics() string {
	if s == nil {
		s = &statistics{}
	}
	stats := "[" + color.CyanString("%d +", s.updates)
	stats = stats + color.RedString(" %d ≡", s.comments)
	stats = stats + color.GreenString(" %d ✓", s.checkListItemUpdates)
	stats = stats + color.MagentaString(" %d …", s.creates)
	stats = stats + "]"
	return stats
}
<|endoftext|>


<commit_before>// timeutil provides a set of time utilities including comparisons,
// conversion to "DT8" int32 and "DT14" int64 formats and other
// capabilities.
package timeutil

import (
	"fmt"
	"strings"
	"time"
)

const (
	DT14               = "20060102150405"
	DT6                = "200601"
	DT8                = "20060102"
	RFC3339Min         = "0000-01-01T00:00:00Z"
	RFC3339Zero        = "0001-01-01T00:00:00Z"
	RFC3339YMD         = "2006-01-02"
	RFC3339YMDZeroUnix = int64(-62135596800)
	ISO8601YM          = "2006-01"
	ISO8601Z2          = "2006-01-02T15:04:05-07"
	ISO8601Z4          = "2006-01-02T15:04:05-0700"
	ISO8601ZCompact    = "20060102T150405Z"
	ISO8601NoTzMilli   = "2006-01-02T15:04:05.000"
)

var FormatMap = map[string]string{
	"RFC3339":    time.RFC3339,
	"RFC3339YMD": RFC3339YMD,
	"ISO8601YM":  ISO8601YM,
}

func GetFormat(formatName string) (string, error) {
	format, ok := FormatMap[strings.TrimSpace(formatName)]
	if !ok {
		return "", fmt.Errorf("Format Not Found: %v", format)
	}
	return format, nil
}

func FormatQuarter(t time.Time) string {
	return fmt.Sprintf("%d Q%d", t.Year(), MonthToQuarter(uint8(t.Month())))
}

func TimeRFC3339Zero() time.Time {
	t0, _ := time.Parse(time.RFC3339, RFC3339Zero)
	return t0
}

type RFC3339YMDTime struct{ time.Time }

type ISO8601NoTzMilliTime struct{ time.Time }

func (t *RFC3339YMDTime) UnmarshalJSON(buf []byte) error {
	tt, isNil, err := timeUnmarshalJSON(buf, RFC3339YMD)
	if err != nil || isNil {
		return err
	}
	t.Time = tt
	return nil
}

func (t RFC3339YMDTime) MarshalJSON() ([]byte, error) {
	return timeMarshalJSON(t.Time, RFC3339YMD)
}

func (t *ISO8601NoTzMilliTime) UnmarshalJSON(buf []byte) error {
	tt, isNil, err := timeUnmarshalJSON(buf, ISO8601NoTzMilli)
	if err != nil || isNil {
		return err
	}
	t.Time = tt
	return nil
}

func (t ISO8601NoTzMilliTime) MarshalJSON() ([]byte, error) {
	return timeMarshalJSON(t.Time, ISO8601NoTzMilli)
}

func timeUnmarshalJSON(buf []byte, format string) (time.Time, bool, error) {
	str := string(buf)
	isNil := true
	if str == "null" || str == "\"\"" {
		return time.Time{}, isNil, nil
	}
	tt, err := time.Parse(format, strings.Trim(str, `"`))
	if err != nil {
		return time.Time{}, false, err
	}
	return tt, false, nil
}

func timeMarshalJSON(t time.Time, format string) ([]byte, error) {
	return []byte(`"` + t.Format(format) + `"`), nil
}
<commit_msg>update timeutil.FormatQuarter()<commit_after>// timeutil provides a set of time utilities including comparisons,
// conversion to "DT8" int32 and "DT14" int64 formats and other
// capabilities.
package timeutil

import (
	"fmt"
	"strings"
	"time"
)

const (
	DT14               = "20060102150405"
	DT6                = "200601"
	DT8                = "20060102"
	RFC3339Min         = "0000-01-01T00:00:00Z"
	RFC3339Zero        = "0001-01-01T00:00:00Z"
	RFC3339YMD         = "2006-01-02"
	RFC3339YMDZeroUnix = int64(-62135596800)
	ISO8601YM          = "2006-01"
	ISO8601Z2          = "2006-01-02T15:04:05-07"
	ISO8601Z4          = "2006-01-02T15:04:05-0700"
	ISO8601ZCompact    = "20060102T150405Z"
	ISO8601NoTzMilli   = "2006-01-02T15:04:05.000"
)

var FormatMap = map[string]string{
	"RFC3339":    time.RFC3339,
	"RFC3339YMD": RFC3339YMD,
	"ISO8601YM":  ISO8601YM,
}

func GetFormat(formatName string) (string, error) {
	format, ok := FormatMap[strings.TrimSpace(formatName)]
	if !ok {
		return "", fmt.Errorf("Format Not Found: %v", format)
	}
	return format, nil
}

// FormatQuarter takes quarter time and formats it using "Q# YYYY".
func FormatQuarter(t time.Time) string {
	return fmt.Sprintf("Q%d %d", MonthToQuarter(uint8(t.Month())), t.Year())
}

func TimeRFC3339Zero() time.Time {
	t0, _ := time.Parse(time.RFC3339, RFC3339Zero)
	return t0
}

type RFC3339YMDTime struct{ time.Time }

type ISO8601NoTzMilliTime struct{ time.Time }

func (t *RFC3339YMDTime) UnmarshalJSON(buf []byte) error {
	tt, isNil, err := timeUnmarshalJSON(buf, RFC3339YMD)
	if err != nil || isNil {
		return err
	}
	t.Time = tt
	return nil
}

func (t RFC3339YMDTime) MarshalJSON() ([]byte, error) {
	return timeMarshalJSON(t.Time, RFC3339YMD)
}

func (t *ISO8601NoTzMilliTime) UnmarshalJSON(buf []byte) error {
	tt, isNil, err := timeUnmarshalJSON(buf, ISO8601NoTzMilli)
	if err != nil || isNil {
		return err
	}
	t.Time = tt
	return nil
}

func (t ISO8601NoTzMilliTime) MarshalJSON() ([]byte, error) {
	return timeMarshalJSON(t.Time, ISO8601NoTzMilli)
}

func timeUnmarshalJSON(buf []byte, format string) (time.Time, bool, error) {
	str := string(buf)
	isNil := true
	if str == "null" || str == "\"\"" {
		return time.Time{}, isNil, nil
	}
	tt, err := time.Parse(format, strings.Trim(str, `"`))
	if err != nil {
		return time.Time{}, false, err
	}
	return tt, false, nil
}

func timeMarshalJSON(t time.Time, format string) ([]byte, error) {
	return []byte(`"` + t.Format(format) + `"`), nil
}
<|endoftext|>


<commit_before>package stats

import (
	"github.com/dhconnelly/rtreego"
	"github.com/guigolab/bamstats/annotation"
	"github.com/guigolab/bamstats/sam"
)

// IHECstats represents statistics for mapped reads
type IHECstats struct {
	Intergenic uint64 `json:"intergenic"`
	RRNA       uint64 `json:"rRNA"`
	index      *annotation.RtreeMap
}

// Merge updates counts from a channel of Stats instances.
func (s *IHECstats) Merge(others chan Stats) {
	for other := range others {
		if other, ok := other.(*IHECstats); ok {
			s.Update(other)
		}
	}
}

// Update updates all counts from a Stats instance.
func (s *IHECstats) Update(other Stats) {
	if other, isIHEC := other.(*IHECstats); isIHEC {
		s.Intergenic += other.Intergenic
		s.RRNA += other.RRNA
	}
}

// Finalize updates dependent counts of a Stats instance.
func (s *IHECstats) Finalize() {
}

// Collect collects general mapping statistics from a sam.Record.
func (s *IHECstats) Collect(record *sam.Record) {
	elements := map[string]uint8{}
	if s.index == nil || !record.IsPrimary() || record.IsUnmapped() {
		return
	}
	mappingLocation := annotation.NewLocation(record.Ref.Name(), record.Start(), record.End())
	rtree := s.index.Get(mappingLocation.Chrom())
	if rtree == nil || rtree.Size() == 0 {
		return
	}

	results := annotation.QueryIndex(rtree, mappingLocation.Start(), mappingLocation.End())

	mappingLocation.GetElements(filterElements(results, mappingLocation.Start(), mappingLocation.End(), 500), elements, "gene_type")

	// if _, isIntergenic := elements["intergenic"]; isIntergenic && len(elements) > 1 {
	// 	fmt.Println(elements)
	// }

	updateIHECcount(elements, s)
}

// NewIHECstats creates a new instance of IHECstats
func NewIHECstats(index *annotation.RtreeMap) *IHECstats {
	return &IHECstats{
		index: index,
	}
}

func filterElements(elements []rtreego.Spatial, start, end, offset float64) []rtreego.Spatial {
	var filteredElements []rtreego.Spatial
	for _, r := range elements {
		if r, ok := r.(*annotation.Feature); ok {
			if r.Element() == "intergenic" {
				if r.End()-r.Start() < 2*offset {
					continue
				}
				if end <= r.Start()+offset || start > r.End()-offset {
					continue
				}
			}
			filteredElements = append(filteredElements, r)
		}
	}
	return filteredElements
}

func updateIHECcount(elems map[string]uint8, st *IHECstats) {

	if len(elems) == 0 {
		return
	}

	rRNAs := []string{
		"rRNA",
		"Mt_rRNA",
	}

	for _, gt := range rRNAs {
		if _, isRRNA := elems[gt]; isRRNA {
			st.RRNA++
		}
	}

	if _, isRRNA := elems["Mt_rRNA"]; isRRNA {
		st.RRNA++
	}

	if _, isIntergenic := elems["intergenic"]; isIntergenic {
		st.Intergenic++
	}

}
<commit_msg>[ci skip] Fix duplicated code in ihec stats<commit_after>package stats

import (
	"github.com/dhconnelly/rtreego"
	"github.com/guigolab/bamstats/annotation"
	"github.com/guigolab/bamstats/sam"
)

// IHECstats represents statistics for mapped reads
type IHECstats struct {
	Intergenic uint64 `json:"intergenic"`
	RRNA       uint64 `json:"rRNA"`
	index      *annotation.RtreeMap
}

// Merge updates counts from a channel of Stats instances.
func (s *IHECstats) Merge(others chan Stats) {
	for other := range others {
		if other, ok := other.(*IHECstats); ok {
			s.Update(other)
		}
	}
}

// Update updates all counts from a Stats instance.
func (s *IHECstats) Update(other Stats) {
	if other, isIHEC := other.(*IHECstats); isIHEC {
		s.Intergenic += other.Intergenic
		s.RRNA += other.RRNA
	}
}

// Finalize updates dependent counts of a Stats instance.
func (s *IHECstats) Finalize() {
}

// Collect collects general mapping statistics from a sam.Record.
func (s *IHECstats) Collect(record *sam.Record) {
	elements := map[string]uint8{}
	if s.index == nil || !record.IsPrimary() || record.IsUnmapped() {
		return
	}
	mappingLocation := annotation.NewLocation(record.Ref.Name(), record.Start(), record.End())
	rtree := s.index.Get(mappingLocation.Chrom())
	if rtree == nil || rtree.Size() == 0 {
		return
	}

	results := annotation.QueryIndex(rtree, mappingLocation.Start(), mappingLocation.End())

	mappingLocation.GetElements(filterElements(results, mappingLocation.Start(), mappingLocation.End(), 500), elements, "gene_type")

	// if _, isIntergenic := elements["intergenic"]; isIntergenic && len(elements) > 1 {
	// 	fmt.Println(elements)
	// }

	updateIHECcount(elements, s)
}

// NewIHECstats creates a new instance of IHECstats
func NewIHECstats(index *annotation.RtreeMap) *IHECstats {
	return &IHECstats{
		index: index,
	}
}

func filterElements(elements []rtreego.Spatial, start, end, offset float64) []rtreego.Spatial {
	var filteredElements []rtreego.Spatial
	for _, r := range elements {
		if r, ok := r.(*annotation.Feature); ok {
			if r.Element() == "intergenic" {
				if r.End()-r.Start() < 2*offset {
					continue
				}
				if end <= r.Start()+offset || start > r.End()-offset {
					continue
				}
			}
			filteredElements = append(filteredElements, r)
		}
	}
	return filteredElements
}

func updateIHECcount(elems map[string]uint8, st *IHECstats) {

	if len(elems) == 0 {
		return
	}

	rRNAs := []string{
		"rRNA",
		"Mt_rRNA",
	}

	for _, gt := range rRNAs {
		if _, isRRNA := elems[gt]; isRRNA {
			st.RRNA++
		}
	}

	if _, isIntergenic := elems["intergenic"]; isIntergenic {
		st.Intergenic++
	}

}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2017 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package cloud

import (
	"context"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/stats"
	"github.com/mitchellh/mapstructure"
	log "github.com/sirupsen/logrus"
)

const (
	TestName           = "k6 test"
	MetricPushInterval = 1 * time.Second
)

// Collector sends result data to the Load Impact cloud service.
type Collector struct {
	config      Config
	referenceID string

	duration   int64
	thresholds map[string][]*stats.Threshold
	client     *Client

	anonymous bool

	sampleBuffer []*Sample
	sampleMu     sync.Mutex

	runStatus chan int
}

// New creates a new cloud collector
func New(conf Config, src *lib.SourceData, opts lib.Options, version string) (*Collector, error) {
	if val, ok := opts.External["loadimpact"]; ok {
		if err := mapstructure.Decode(val, &conf); err != nil {
			return nil, err
		}
	}

	if conf.Name == "" {
		conf.Name = filepath.Base(src.Filename)
	}
	if conf.Name == "-" {
		conf.Name = TestName
	}

	thresholds := make(map[string][]*stats.Threshold)
	for name, t := range opts.Thresholds {
		thresholds[name] = append(thresholds[name], t.Thresholds...)
	}

	// Sum test duration from options. -1 for unknown duration.
	var duration int64 = -1
	if len(opts.Stages) > 0 {
		duration = sumStages(opts.Stages)
	} else if opts.Duration.Valid {
		duration = int64(time.Duration(opts.Duration.Duration).Seconds())
	}

	if conf.Token == "" && conf.DeprecatedToken != "" {
		log.Warn("K6CLOUD_TOKEN is deprecated and will be removed. Use K6_CLOUD_TOKEN instead.")
		conf.Token = conf.DeprecatedToken
	}

	return &Collector{
		config:     conf,
		thresholds: thresholds,
		client:     NewClient(conf.Token, conf.Host, version),
		anonymous:  conf.Token == "",
		duration:   duration,
		runStatus:  make(chan int, 1),
	}, nil
}

func (c *Collector) Init() error {
	thresholds := make(map[string][]string)

	for name, t := range c.thresholds {
		for _, threshold := range t {
			thresholds[name] = append(thresholds[name], threshold.Source)
		}
	}

	testRun := &TestRun{
		Name:       c.config.Name,
		ProjectID:  c.config.ProjectID,
		Thresholds: thresholds,
		Duration:   c.duration,
	}

	response, err := c.client.CreateTestRun(testRun)

	if err != nil {
		return err
	}
	c.referenceID = response.ReferenceID

	log.WithFields(log.Fields{
		"name":        c.config.Name,
		"projectId":   c.config.ProjectID,
		"duration":    c.duration,
		"referenceId": c.referenceID,
	}).Debug("Cloud: Initialized")
	return nil
}

func (c *Collector) Link() string {
	return URLForResults(c.referenceID, c.config)
}

func (c *Collector) Run(ctx context.Context) {
	timer := time.NewTicker(MetricPushInterval)

	for {
		select {
		case <-timer.C:
			c.pushMetrics()
		case <-ctx.Done():
			c.pushMetrics()
			c.testFinished()
			return
		}
	}
}

func (c *Collector) IsReady() bool {
	return true
}

func (c *Collector) Collect(samples []stats.Sample) {
	if c.referenceID == "" {
		return
	}

	var cloudSamples []*Sample
	var httpJSON *Sample
	var iterationJSON *Sample
	for _, samp := range samples {

		name := samp.Metric.Name
		if name == "http_reqs" {
			httpJSON = &Sample{
				Type:   "Points",
				Metric: "http_req_li_all",
				Data: SampleData{
					Type:   samp.Metric.Type,
					Time:   Timestamp(samp.Time),
					Tags:   samp.Tags,
					Values: make(map[string]float64),
				},
			}
			httpJSON.Data.Values[name] = samp.Value
			cloudSamples = append(cloudSamples, httpJSON)
		} else if name == "data_sent" {
			iterationJSON = &Sample{
				Type:   "Points",
				Metric: "iter_li_all",
				Data: SampleData{
					Type:   samp.Metric.Type,
					Time:   Timestamp(samp.Time),
					Tags:   samp.Tags,
					Values: make(map[string]float64),
				},
			}
			iterationJSON.Data.Values[name] = samp.Value
			cloudSamples = append(cloudSamples, iterationJSON)
		} else if name == "data_received" || name == "iteration_duration" {
			//TODO: make sure that tags match
			iterationJSON.Data.Values[name] = samp.Value
		} else if strings.HasPrefix(name, "http_req_") {
			//TODO: make sure that tags match
			httpJSON.Data.Values[name] = samp.Value
		} else {
			sampleJSON := &Sample{
				Type:   "Point",
				Metric: name,
				Data: SampleData{
					Type:  samp.Metric.Type,
					Time:  Timestamp(samp.Time),
					Value: samp.Value,
					Tags:  samp.Tags,
				},
			}
			cloudSamples = append(cloudSamples, sampleJSON)
		}
	}

	if len(cloudSamples) > 0 {
		c.sampleMu.Lock()
		c.sampleBuffer = append(c.sampleBuffer, cloudSamples...)
		c.sampleMu.Unlock()
	}
}

func (c *Collector) pushMetrics() {
	c.sampleMu.Lock()
	if len(c.sampleBuffer) == 0 {
		c.sampleMu.Unlock()
		return
	}
	buffer := c.sampleBuffer
	c.sampleBuffer = nil
	c.sampleMu.Unlock()

	log.WithFields(log.Fields{
		"samples": len(buffer),
	}).Debug("Pushing metrics to cloud")

	err := c.client.PushMetric(c.referenceID, c.config.NoCompress, buffer)
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Warn("Failed to send metrics to cloud")
	}
}

func (c *Collector) testFinished() {
	if c.referenceID == "" {
		return
	}

	testTainted := false
	thresholdResults := make(ThresholdResult)
	for name, thresholds := range c.thresholds {
		thresholdResults[name] = make(map[string]bool)
		for _, t := range thresholds {
			thresholdResults[name][t.Source] = t.Failed
			if t.Failed {
				testTainted = true
			}
		}
	}

	log.WithFields(log.Fields{
		"ref":     c.referenceID,
		"tainted": testTainted,
	}).Debug("Sending test finished")

	runStatus := lib.RunStatusFinished
	select {
	case status := <-c.runStatus:
		runStatus = status
	case <-time.After(100 * time.Millisecond):
	}

	err := c.client.TestFinished(c.referenceID, thresholdResults, testTainted, runStatus)
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Warn("Failed to send test finished to cloud")
	}
}

func sumStages(stages []lib.Stage) int64 {
	var total time.Duration
	for _, stage := range stages {
		total += time.Duration(stage.Duration.Duration)
	}

	return int64(total.Seconds())
}

// GetRequiredSystemTags returns which sample tags are needed by this collector
func (c *Collector) GetRequiredSystemTags() lib.TagSet {
	return lib.GetTagSet("name", "method", "status", "error", "check", "group")
}

func (c *Collector) SetRunStatus(status int) {
	c.runStatus <- status
}
<commit_msg>Adding debug statement run status receiving<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2017 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package cloud

import (
	"context"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/stats"
	"github.com/mitchellh/mapstructure"
	log "github.com/sirupsen/logrus"
)

const (
	TestName           = "k6 test"
	MetricPushInterval = 1 * time.Second
)

// Collector sends result data to the Load Impact cloud service.
type Collector struct {
	config      Config
	referenceID string

	duration   int64
	thresholds map[string][]*stats.Threshold
	client     *Client

	anonymous bool

	sampleBuffer []*Sample
	sampleMu     sync.Mutex

	runStatus chan int
}

// New creates a new cloud collector
func New(conf Config, src *lib.SourceData, opts lib.Options, version string) (*Collector, error) {
	if val, ok := opts.External["loadimpact"]; ok {
		if err := mapstructure.Decode(val, &conf); err != nil {
			return nil, err
		}
	}

	if conf.Name == "" {
		conf.Name = filepath.Base(src.Filename)
	}
	if conf.Name == "-" {
		conf.Name = TestName
	}

	thresholds := make(map[string][]*stats.Threshold)
	for name, t := range opts.Thresholds {
		thresholds[name] = append(thresholds[name], t.Thresholds...)
	}

	// Sum test duration from options. -1 for unknown duration.
	var duration int64 = -1
	if len(opts.Stages) > 0 {
		duration = sumStages(opts.Stages)
	} else if opts.Duration.Valid {
		duration = int64(time.Duration(opts.Duration.Duration).Seconds())
	}

	if conf.Token == "" && conf.DeprecatedToken != "" {
		log.Warn("K6CLOUD_TOKEN is deprecated and will be removed. Use K6_CLOUD_TOKEN instead.")
		conf.Token = conf.DeprecatedToken
	}

	return &Collector{
		config:     conf,
		thresholds: thresholds,
		client:     NewClient(conf.Token, conf.Host, version),
		anonymous:  conf.Token == "",
		duration:   duration,
		runStatus:  make(chan int, 1),
	}, nil
}

func (c *Collector) Init() error {
	thresholds := make(map[string][]string)

	for name, t := range c.thresholds {
		for _, threshold := range t {
			thresholds[name] = append(thresholds[name], threshold.Source)
		}
	}

	testRun := &TestRun{
		Name:       c.config.Name,
		ProjectID:  c.config.ProjectID,
		Thresholds: thresholds,
		Duration:   c.duration,
	}

	response, err := c.client.CreateTestRun(testRun)

	if err != nil {
		return err
	}
	c.referenceID = response.ReferenceID

	log.WithFields(log.Fields{
		"name":        c.config.Name,
		"projectId":   c.config.ProjectID,
		"duration":    c.duration,
		"referenceId": c.referenceID,
	}).Debug("Cloud: Initialized")
	return nil
}

func (c *Collector) Link() string {
	return URLForResults(c.referenceID, c.config)
}

func (c *Collector) Run(ctx context.Context) {
	timer := time.NewTicker(MetricPushInterval)

	for {
		select {
		case <-timer.C:
			c.pushMetrics()
		case <-ctx.Done():
			c.pushMetrics()
			c.testFinished()
			return
		}
	}
}

func (c *Collector) IsReady() bool {
	return true
}

func (c *Collector) Collect(samples []stats.Sample) {
	if c.referenceID == "" {
		return
	}

	var cloudSamples []*Sample
	var httpJSON *Sample
	var iterationJSON *Sample
	for _, samp := range samples {

		name := samp.Metric.Name
		if name == "http_reqs" {
			httpJSON = &Sample{
				Type:   "Points",
				Metric: "http_req_li_all",
				Data: SampleData{
					Type:   samp.Metric.Type,
					Time:   Timestamp(samp.Time),
					Tags:   samp.Tags,
					Values: make(map[string]float64),
				},
			}
			httpJSON.Data.Values[name] = samp.Value
			cloudSamples = append(cloudSamples, httpJSON)
		} else if name == "data_sent" {
			iterationJSON = &Sample{
				Type:   "Points",
				Metric: "iter_li_all",
				Data: SampleData{
					Type:   samp.Metric.Type,
					Time:   Timestamp(samp.Time),
					Tags:   samp.Tags,
					Values: make(map[string]float64),
				},
			}
			iterationJSON.Data.Values[name] = samp.Value
			cloudSamples = append(cloudSamples, iterationJSON)
		} else if name == "data_received" || name == "iteration_duration" {
			//TODO: make sure that tags match
			iterationJSON.Data.Values[name] = samp.Value
		} else if strings.HasPrefix(name, "http_req_") {
			//TODO: make sure that tags match
			httpJSON.Data.Values[name] = samp.Value
		} else {
			sampleJSON := &Sample{
				Type:   "Point",
				Metric: name,
				Data: SampleData{
					Type:  samp.Metric.Type,
					Time:  Timestamp(samp.Time),
					Value: samp.Value,
					Tags:  samp.Tags,
				},
			}
			cloudSamples = append(cloudSamples, sampleJSON)
		}
	}

	if len(cloudSamples) > 0 {
		c.sampleMu.Lock()
		c.sampleBuffer = append(c.sampleBuffer, cloudSamples...)
		c.sampleMu.Unlock()
	}
}

func (c *Collector) pushMetrics() {
	c.sampleMu.Lock()
	if len(c.sampleBuffer) == 0 {
		c.sampleMu.Unlock()
		return
	}
	buffer := c.sampleBuffer
	c.sampleBuffer = nil
	c.sampleMu.Unlock()

	log.WithFields(log.Fields{
		"samples": len(buffer),
	}).Debug("Pushing metrics to cloud")

	err := c.client.PushMetric(c.referenceID, c.config.NoCompress, buffer)
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Warn("Failed to send metrics to cloud")
	}
}

func (c *Collector) testFinished() {
	if c.referenceID == "" {
		return
	}

	testTainted := false
	thresholdResults := make(ThresholdResult)
	for name, thresholds := range c.thresholds {
		thresholdResults[name] = make(map[string]bool)
		for _, t := range thresholds {
			thresholdResults[name][t.Source] = t.Failed
			if t.Failed {
				testTainted = true
			}
		}
	}

	log.WithFields(log.Fields{
		"ref":     c.referenceID,
		"tainted": testTainted,
	}).Debug("Sending test finished")

	runStatus := lib.RunStatusFinished
	select {
	case status := <-c.runStatus:
		runStatus = status
	case <-time.After(100 * time.Millisecond):
		log.WithFields(log.Fields{
			"ref": c.referenceID,
		}).Debug("Receiving run test status timed out")
	}

	err := c.client.TestFinished(c.referenceID, thresholdResults, testTainted, runStatus)
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Warn("Failed to send test finished to cloud")
	}
}

func sumStages(stages []lib.Stage) int64 {
	var total time.Duration
	for _, stage := range stages {
		total += time.Duration(stage.Duration.Duration)
	}

	return int64(total.Seconds())
}

// GetRequiredSystemTags returns which sample tags are needed by this collector
func (c *Collector) GetRequiredSystemTags() lib.TagSet {
	return lib.GetTagSet("name", "method", "status", "error", "check", "group")
}

func (c *Collector) SetRunStatus(status int) {
	c.runStatus <- status
}
<|endoftext|>


<commit_before>package geom

// A Point represents a single point.
type Point struct {
	geom0
}

// NewPoint allocates a new Point with layout l and all values zero.
func NewPoint(l Layout) *Point {
	return NewPointFlat(l, make([]float64, l.Stride()))
}

// NewPointFlat allocates a new Point with layout l and flat coordinates flatCoords.
func NewPointFlat(l Layout, flatCoords []float64) *Point {
	p := new(Point)
	p.layout = l
	p.stride = l.Stride()
	p.flatCoords = flatCoords
	return p
}

// Area returns p's area, i.e. zero.
func (p *Point) Area() float64 {
	return 0
}

// Clone returns a copy of p that does not alias p.
func (p *Point) Clone() *Point {
	return deriveClonePoint(p)
}

// Empty returns false.
func (p *Point) Empty() bool {
	return false
}

// Length returns the length of p, i.e. zero.
func (p *Point) Length() float64 {
	return 0
}

// MustSetCoords is like SetCoords but panics on any error.
func (p *Point) MustSetCoords(coords Coord) *Point {
	Must(p.SetCoords(coords))
	return p
}

// SetCoords sets the coordinates of p.
func (p *Point) SetCoords(coords Coord) (*Point, error) {
	if err := p.setCoords(coords); err != nil {
		return nil, err
	}
	return p, nil
}

// SetSRID sets the SRID of p.
func (p *Point) SetSRID(srid int) *Point {
	p.srid = srid
	return p
}

// Swap swaps the values of p and p2.
func (p *Point) Swap(p2 *Point) {
	*p, *p2 = *p2, *p
}

// X returns p's X-coordinate.
func (p *Point) X() float64 {
	return p.flatCoords[0]
}

// Y returns p's Y-coordinate.
func (p *Point) Y() float64 {
	return p.flatCoords[1]
}

// Z returns p's Z-coordinate, or zero if p has no Z-coordinate.
func (p *Point) Z() float64 {
	zIndex := p.layout.ZIndex()
	if zIndex == -1 {
		return 0
	}
	return p.flatCoords[zIndex]
}

// M returns p's M-coordinate, or zero if p has no M-coordinate.
func (p *Point) M() float64 {
	mIndex := p.layout.MIndex()
	if mIndex == -1 {
		return 0
	}
	return p.flatCoords[mIndex]
}
<commit_msg>Use g as receiver name in Point<commit_after>package geom

// A Point represents a single point.
type Point struct {
	geom0
}

// NewPoint allocates a new Point with layout l and all values zero.
func NewPoint(l Layout) *Point {
	return NewPointFlat(l, make([]float64, l.Stride()))
}

// NewPointFlat allocates a new Point with layout l and flat coordinates flatCoords.
func NewPointFlat(l Layout, flatCoords []float64) *Point {
	g := new(Point)
	g.layout = l
	g.stride = l.Stride()
	g.flatCoords = flatCoords
	return g
}

// Area returns g's area, i.e. zero.
func (g *Point) Area() float64 {
	return 0
}

// Clone returns a copy of g that does not alias g.
func (g *Point) Clone() *Point {
	return deriveClonePoint(g)
}

// Empty returns false.
func (g *Point) Empty() bool {
	return false
}

// Length returns the length of g, i.e. zero.
func (g *Point) Length() float64 {
	return 0
}

// MustSetCoords is like SetCoords but panics on any error.
func (g *Point) MustSetCoords(coords Coord) *Point {
	Must(g.SetCoords(coords))
	return g
}

// SetCoords sets the coordinates of g.
func (g *Point) SetCoords(coords Coord) (*Point, error) {
	if err := g.setCoords(coords); err != nil {
		return nil, err
	}
	return g, nil
}

// SetSRID sets the SRID of g.
func (g *Point) SetSRID(srid int) *Point {
	g.srid = srid
	return g
}

// Swap swaps the values of g and g2.
func (g *Point) Swap(g2 *Point) {
	*g, *g2 = *g2, *g
}

// X returns g's X-coordinate.
func (g *Point) X() float64 {
	return g.flatCoords[0]
}

// Y returns g's Y-coordinate.
func (g *Point) Y() float64 {
	return g.flatCoords[1]
}

// Z returns g's Z-coordinate, or zero if g has no Z-coordinate.
func (g *Point) Z() float64 {
	zIndex := g.layout.ZIndex()
	if zIndex == -1 {
		return 0
	}
	return g.flatCoords[zIndex]
}

// M returns g's M-coordinate, or zero if g has no M-coordinate.
func (g *Point) M() float64 {
	mIndex := g.layout.MIndex()
	if mIndex == -1 {
		return 0
	}
	return g.flatCoords[mIndex]
}
<|endoftext|>