<commit_before>package api

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"io"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/ponzu-cms/ponzu/system/api/analytics"
	"github.com/ponzu-cms/ponzu/system/db"
	"github.com/ponzu-cms/ponzu/system/item"
)

// deprecating from API, but going to provide code here in case someone wants it
func typesHandler(res http.ResponseWriter, req *http.Request) {
	var types = []string{}
	for t, fn := range item.Types {
		if !hide(fn(), res, req) {
			types = append(types, t)
		}
	}

	j, err := toJSON(types)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	sendData(res, req, j, http.StatusOK)
}

func contentsHandler(res http.ResponseWriter, req *http.Request) {
	q := req.URL.Query()
	t := q.Get("type")
	if t == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	it, ok := item.Types[t]
	if !ok {
		res.WriteHeader(http.StatusNotFound)
		return
	}

	if hide(it(), res, req) {
		return
	}

	count, err := strconv.Atoi(q.Get("count")) // int: determines number of posts to return (10 default, -1 is all)
	if err != nil {
		if q.Get("count") == "" {
			count = 10
		} else {
			res.WriteHeader(http.StatusInternalServerError)
			return
		}
	}

	offset, err := strconv.Atoi(q.Get("offset")) // int: multiplier of count for pagination (0 default)
	if err != nil {
		if q.Get("offset") == "" {
			offset = 0
		} else {
			res.WriteHeader(http.StatusInternalServerError)
			return
		}
	}

	order := strings.ToLower(q.Get("order")) // string: sort order of posts by timestamp ASC / DESC (DESC default)
	if order != "asc" {
		order = "desc"
	}

	opts := db.QueryOptions{
		Count:  count,
		Offset: offset,
		Order:  order,
	}

	_, bb := db.Query(t+"__sorted", opts)
	var result = []json.RawMessage{}
	for i := range bb {
		result = append(result, bb[i])
	}

	j, err := fmtJSON(result...)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	sendData(res, req, j, http.StatusOK)
}

func contentHandler(res http.ResponseWriter, req *http.Request) {
	q := req.URL.Query()
	id := q.Get("id")
	t := q.Get("type")
	slug := q.Get("slug")

	if slug != "" {
		contentHandlerBySlug(res, req)
		return
	}

	if t == "" || id == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	pt, ok := item.Types[t]
	if !ok {
		res.WriteHeader(http.StatusNotFound)
		return
	}

	if hide(pt(), res, req) {
		return
	}

	post, err := db.Content(t + ":" + id)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	defer push(res, req, pt, post)

	j, err := fmtJSON(json.RawMessage(post))
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	sendData(res, req, j, http.StatusOK)
}

func contentHandlerBySlug(res http.ResponseWriter, req *http.Request) {
	slug := req.URL.Query().Get("slug")

	if slug == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	// lookup type:id by slug key in __contentIndex
	t, post, err := db.ContentBySlug(slug)
	if err != nil {
		log.Println("Error finding content by slug:", slug, err)
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	it, ok := item.Types[t]
	if !ok {
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	if hide(it(), res, req) {
		return
	}

	defer push(res, req, it, post)

	j, err := fmtJSON(json.RawMessage(post))
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	sendData(res, req, j, http.StatusOK)
}

func hide(it interface{}, res http.ResponseWriter, req *http.Request) bool {
	// check if should be hidden
	if h, ok := it.(item.Hideable); ok {
		err := h.Hide(req)
		if err == item.ErrAllowHiddenItem {
			return false
		}

		if err != nil {
			res.WriteHeader(http.StatusInternalServerError)
			return true
		}

		res.WriteHeader(http.StatusNotFound)
		return true
	}

	return false
}

func fmtJSON(data ...json.RawMessage) ([]byte, error) {
	var msg = []json.RawMessage{}
	for _, d := range data {
		msg = append(msg, d)
	}

	resp := map[string][]json.RawMessage{
		"data": msg,
	}

	var buf = &bytes.Buffer{}
	enc := json.NewEncoder(buf)
	err := enc.Encode(resp)
	if err != nil {
		log.Println("Failed to encode data to JSON:", err)
		return nil, err
	}

	return buf.Bytes(), nil
}

func toJSON(data []string) ([]byte, error) {
	var buf = &bytes.Buffer{}
	enc := json.NewEncoder(buf)
	resp := map[string][]string{
		"data": data,
	}

	err := enc.Encode(resp)
	if err != nil {
		log.Println("Failed to encode data to JSON:", err)
		return nil, err
	}

	return buf.Bytes(), nil
}

// sendData should be used any time you want to communicate
// data back to a foreign client
func sendData(res http.ResponseWriter, req *http.Request, data []byte, code int) {
	res, cors := responseWithCORS(res, req)
	if !cors {
		return
	}

	res.Header().Set("Content-Type", "application/json")
	res.WriteHeader(code)

	_, err := res.Write(data)
	if err != nil {
		log.Println("Error writing to response in sendData")
	}
}

// sendPreflight is used to respond to a cross-origin "OPTIONS" request
func sendPreflight(res http.ResponseWriter) {
	res.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type")
	res.Header().Set("Access-Control-Allow-Origin", "*")
	res.WriteHeader(200)
	return
}

func responseWithCORS(res http.ResponseWriter, req *http.Request) (http.ResponseWriter, bool) {
	if db.ConfigCache("cors_disabled").(bool) == true {
		// check origin matches config domain and Allow
		domain := db.ConfigCache("domain").(string)
		origin := req.Header.Get("Origin")
		u, err := url.Parse(origin)
		if err != nil {
			log.Println("Error parsing URL from request Origin header:", origin)
			return res, false
		}

		origin = u.Host

		// currently, this will check for exact match. will need feedback to
		// determine if subdomains should be allowed or allow multiple domains
		// in config
		if origin == domain {
			// apply limited CORS headers and return
			res.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type")
			res.Header().Set("Access-Control-Allow-Origin", domain)
			return res, true
		}

		// disallow request
		res.WriteHeader(http.StatusForbidden)
		return res, false
	}

	// apply full CORS headers and return
	res.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type")
	res.Header().Set("Access-Control-Allow-Origin", "*")

	return res, true
}

// CORS wraps a HandlerFunc to respond to OPTIONS requests properly
func CORS(next http.HandlerFunc) http.HandlerFunc {
	return db.CacheControl(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.Method == http.MethodOptions {
			sendPreflight(res)
			return
		}

		next.ServeHTTP(res, req)
	}))
}

// Record wraps a HandlerFunc to record API requests for analytical purposes
func Record(next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		go analytics.Record(req)

		next.ServeHTTP(res, req)
	})
}

// Gzip wraps a HandlerFunc to compress responses when possible
func Gzip(next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		// check if req header content-encoding supports gzip
		if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
			// gzip response data
			gzres := gzipResponseWriter{
				rw: res,
				gw: gzip.NewWriter(res),
			}

			next.ServeHTTP(gzres, req)
		}

		next.ServeHTTP(res, req)
	})
}

type gzipResponseWriter struct {
	rw http.ResponseWriter
	gw io.Writer
}

func (gzw gzipResponseWriter) Header() http.Header {
	return gzw.rw.Header()
}

func (gzw gzipResponseWriter) Write(p []byte) (int, error) {
	return gzw.gw.Write(p)
}

func (gzw gzipResponseWriter) WriteHeader(code int) {
	gzw.rw.WriteHeader(code)
}
<commit_msg>fix gzip middleware and remove code from sendData func<commit_after>package api

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/ponzu-cms/ponzu/system/api/analytics"
	"github.com/ponzu-cms/ponzu/system/db"
	"github.com/ponzu-cms/ponzu/system/item"
)

// deprecating from API, but going to provide code here in case someone wants it
func typesHandler(res http.ResponseWriter, req *http.Request) {
	var types = []string{}
	for t, fn := range item.Types {
		if !hide(fn(), res, req) {
			types = append(types, t)
		}
	}

	j, err := toJSON(types)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	sendData(res, req, j)
}

func contentsHandler(res http.ResponseWriter, req *http.Request) {
	q := req.URL.Query()
	t := q.Get("type")
	if t == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	it, ok := item.Types[t]
	if !ok {
		res.WriteHeader(http.StatusNotFound)
		return
	}

	if hide(it(), res, req) {
		return
	}

	count, err := strconv.Atoi(q.Get("count")) // int: determines number of posts to return (10 default, -1 is all)
	if err != nil {
		if q.Get("count") == "" {
			count = 10
		} else {
			res.WriteHeader(http.StatusInternalServerError)
			return
		}
	}

	offset, err := strconv.Atoi(q.Get("offset")) // int: multiplier of count for pagination (0 default)
	if err != nil {
		if q.Get("offset") == "" {
			offset = 0
		} else {
			res.WriteHeader(http.StatusInternalServerError)
			return
		}
	}

	order := strings.ToLower(q.Get("order")) // string: sort order of posts by timestamp ASC / DESC (DESC default)
	if order != "asc" {
		order = "desc"
	}

	opts := db.QueryOptions{
		Count:  count,
		Offset: offset,
		Order:  order,
	}

	_, bb := db.Query(t+"__sorted", opts)
	var result = []json.RawMessage{}
	for i := range bb {
		result = append(result, bb[i])
	}

	j, err := fmtJSON(result...)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	sendData(res, req, j)
}

func contentHandler(res http.ResponseWriter, req *http.Request) {
	q := req.URL.Query()
	id := q.Get("id")
	t := q.Get("type")
	slug := q.Get("slug")

	if slug != "" {
		contentHandlerBySlug(res, req)
		return
	}

	if t == "" || id == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	pt, ok := item.Types[t]
	if !ok {
		res.WriteHeader(http.StatusNotFound)
		return
	}

	if hide(pt(), res, req) {
		return
	}

	post, err := db.Content(t + ":" + id)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	defer push(res, req, pt, post)

	j, err := fmtJSON(json.RawMessage(post))
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	sendData(res, req, j)
}

func contentHandlerBySlug(res http.ResponseWriter, req *http.Request) {
	slug := req.URL.Query().Get("slug")

	if slug == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	// lookup type:id by slug key in __contentIndex
	t, post, err := db.ContentBySlug(slug)
	if err != nil {
		log.Println("Error finding content by slug:", slug, err)
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	it, ok := item.Types[t]
	if !ok {
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	if hide(it(), res, req) {
		return
	}

	defer push(res, req, it, post)

	j, err := fmtJSON(json.RawMessage(post))
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	sendData(res, req, j)
}

func hide(it interface{}, res http.ResponseWriter, req *http.Request) bool {
	// check if should be hidden
	if h, ok := it.(item.Hideable); ok {
		err := h.Hide(req)
		if err == item.ErrAllowHiddenItem {
			return false
		}

		if err != nil {
			res.WriteHeader(http.StatusInternalServerError)
			return true
		}

		res.WriteHeader(http.StatusNotFound)
		return true
	}

	return false
}

func fmtJSON(data ...json.RawMessage) ([]byte, error) {
	var msg = []json.RawMessage{}
	for _, d := range data {
		msg = append(msg, d)
	}

	resp := map[string][]json.RawMessage{
		"data": msg,
	}

	var buf = &bytes.Buffer{}
	enc := json.NewEncoder(buf)
	err := enc.Encode(resp)
	if err != nil {
		log.Println("Failed to encode data to JSON:", err)
		return nil, err
	}

	return buf.Bytes(), nil
}

func toJSON(data []string) ([]byte, error) {
	var buf = &bytes.Buffer{}
	enc := json.NewEncoder(buf)
	resp := map[string][]string{
		"data": data,
	}

	err := enc.Encode(resp)
	if err != nil {
		log.Println("Failed to encode data to JSON:", err)
		return nil, err
	}

	return buf.Bytes(), nil
}

// sendData should be used any time you want to communicate
// data back to a foreign client
func sendData(res http.ResponseWriter, req *http.Request, data []byte) {
	res.Header().Set("Content-Type", "application/json")
	res.Header().Set("Vary", "Accept-Encoding")

	_, err := res.Write(data)
	if err != nil {
		log.Println("Error writing to response in sendData")
	}
}

// sendPreflight is used to respond to a cross-origin "OPTIONS" request
func sendPreflight(res http.ResponseWriter) {
	res.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type")
	res.Header().Set("Access-Control-Allow-Origin", "*")
	res.WriteHeader(200)
	return
}

func responseWithCORS(res http.ResponseWriter, req *http.Request) (http.ResponseWriter, bool) {
	if db.ConfigCache("cors_disabled").(bool) == true {
		// check origin matches config domain
		domain := db.ConfigCache("domain").(string)
		origin := req.Header.Get("Origin")
		u, err := url.Parse(origin)
		if err != nil {
			log.Println("Error parsing URL from request Origin header:", origin)
			return res, false
		}

		// hack to get dev environments to bypass cors since u.Host (below) will
		// be empty, based on Go's url.Parse function
		if domain == "localhost" {
			domain = ""
		}
		origin = u.Host

		// currently, this will check for exact match. will need feedback to
		// determine if subdomains should be allowed or allow multiple domains
		// in config
		if origin == domain {
			// apply limited CORS headers and return
			res.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type")
			res.Header().Set("Access-Control-Allow-Origin", domain)
			return res, true
		}

		// disallow request
		res.WriteHeader(http.StatusForbidden)
		return res, false
	}

	// apply full CORS headers and return
	res.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type")
	res.Header().Set("Access-Control-Allow-Origin", "*")

	return res, true
}

// CORS wraps a HandlerFunc to respond to OPTIONS requests properly
func CORS(next http.HandlerFunc) http.HandlerFunc {
	return db.CacheControl(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		res, cors := responseWithCORS(res, req)
		if !cors {
			return
		}

		if req.Method == http.MethodOptions {
			sendPreflight(res)
			return
		}

		next.ServeHTTP(res, req)
	}))
}

// Record wraps a HandlerFunc to record API requests for analytical purposes
func Record(next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		go analytics.Record(req)

		next.ServeHTTP(res, req)
	})
}

// Gzip wraps a HandlerFunc to compress responses when possible
func Gzip(next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		// check if req header content-encoding supports gzip
		if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
			// gzip response data
			res.Header().Set("Content-Encoding", "gzip")
			gzres := gzipResponseWriter{res, gzip.NewWriter(res)}

			next.ServeHTTP(gzres, req)

			return
		}

		next.ServeHTTP(res, req)
	})
}

type gzipResponseWriter struct {
	http.ResponseWriter
	gw *gzip.Writer
}

func (gzw gzipResponseWriter) Write(p []byte) (int, error) {
	defer gzw.gw.Close()
	return gzw.gw.Write(p)
}
<|endoftext|>


<commit_before>package testutil

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"v.io/x/devtools/lib/collect"
	"v.io/x/devtools/lib/util"
)

const (
	numAttempts = 3
)

// vanadiumBootstrap runs a test of Vanadium bootstrapping.
func vanadiumBootstrap(ctx *util.Context, testName string, _ ...TestOpt) (_ *TestResult, e error) {
	// Initialize the test.
	cleanup, err := initTest(ctx, testName, nil)
	if err != nil {
		return nil, internalTestError{err, "Init"}
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	// Create a new temporary VANADIUM_ROOT.
	oldRoot := os.Getenv("VANADIUM_ROOT")
	defer collect.Error(func() error { return os.Setenv("VANADIUM_ROOT", oldRoot) }, &e)
	tmpDir, err := ctx.Run().TempDir("", "")
	if err != nil {
		return nil, internalTestError{err, "TempDir"}
	}
	defer collect.Error(func() error { return ctx.Run().RemoveAll(tmpDir) }, &e)

	if err := os.Setenv("VANADIUM_ROOT", tmpDir); err != nil {
		return nil, internalTestError{err, "Setenv"}
	}

	// Run the setup script.
	var out bytes.Buffer
	opts := ctx.Run().Opts()
	opts.Stdout = io.MultiWriter(opts.Stdout, &out)
	opts.Stderr = io.MultiWriter(opts.Stderr, &out)
	for i := 1; i <= numAttempts; i++ {
		if i > 1 {
			fmt.Fprintf(ctx.Stdout(), "Attempt %d/%d:\n", i, numAttempts)
		}
		if err = ctx.Run().CommandWithOpts(opts, filepath.Join(oldRoot, "scripts", "setup", "vanadium")); err == nil {
			break
		}
	}
	if err != nil {
		// Create xUnit report.
		suites := []testSuite{}
		s := createTestSuiteWithFailure("VanadiumGo", "bootstrap", "Vanadium bootstrapping failed", out.String(), 0)
		suites = append(suites, *s)
		if err := createXUnitReport(ctx, testName, suites); err != nil {
			return nil, err
		}
		return &TestResult{Status: TestFailed}, nil
	}
	return &TestResult{Status: TestPassed}, nil
}
<commit_msg>TBR devtools/lib/testutil: a fix for the vanadium-bootstrap test target<commit_after>package testutil

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"v.io/x/devtools/lib/collect"
	"v.io/x/devtools/lib/util"
)

const (
	numAttempts = 3
)

// vanadiumBootstrap runs a test of Vanadium bootstrapping.
func vanadiumBootstrap(ctx *util.Context, testName string, _ ...TestOpt) (_ *TestResult, e error) {
	// Initialize the test.
	cleanup, err := initTest(ctx, testName, nil)
	if err != nil {
		return nil, internalTestError{err, "Init"}
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	// Create a new temporary VANADIUM_ROOT.
	oldRoot := os.Getenv("VANADIUM_ROOT")
	defer collect.Error(func() error { return os.Setenv("VANADIUM_ROOT", oldRoot) }, &e)
	tmpDir, err := ctx.Run().TempDir("", "")
	if err != nil {
		return nil, internalTestError{err, "TempDir"}
	}
	defer collect.Error(func() error { return ctx.Run().RemoveAll(tmpDir) }, &e)

	root := filepath.Join(tmpDir, "root")
	if err := os.Setenv("VANADIUM_ROOT", root); err != nil {
		return nil, internalTestError{err, "Setenv"}
	}

	// Run the setup script.
	var out bytes.Buffer
	opts := ctx.Run().Opts()
	opts.Stdout = io.MultiWriter(opts.Stdout, &out)
	opts.Stderr = io.MultiWriter(opts.Stderr, &out)
	for i := 1; i <= numAttempts; i++ {
		if i > 1 {
			fmt.Fprintf(ctx.Stdout(), "Attempt %d/%d:\n", i, numAttempts)
		}
		if err = ctx.Run().CommandWithOpts(opts, filepath.Join(oldRoot, "scripts", "setup", "vanadium")); err == nil {
			break
		}
	}
	if err != nil {
		// Create xUnit report.
		suites := []testSuite{}
		s := createTestSuiteWithFailure("VanadiumGo", "bootstrap", "Vanadium bootstrapping failed", out.String(), 0)
		suites = append(suites, *s)
		if err := createXUnitReport(ctx, testName, suites); err != nil {
			return nil, err
		}
		return &TestResult{Status: TestFailed}, nil
	}
	return &TestResult{Status: TestPassed}, nil
}
<|endoftext|>


<commit_before>package testutil

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"v.io/tools/lib/collect"
	"v.io/tools/lib/util"
)

var (
	jenkinsHost = "http://veyron-jenkins:8001/jenkins"
	netrcFile   = filepath.Join(os.Getenv("HOME"), ".netrc")
)

const (
	dummyTestResult = `<?xml version="1.0" encoding="utf-8"?>
<!--
  This file will be used to generate a dummy test results file
  in case the presubmit tests produce no test result files.
-->
<testsuites>
  <testsuite name="NO_TESTS" tests="1" errors="0" failures="0" skip="0">
    <testcase classname="NO_TESTS" name="NO_TESTS" time="0">
    </testcase>
  </testsuite>
</testsuites>
`
)

// findTestResultFiles returns a slice of paths to presubmit test
// results.
func findTestResultFiles(ctx *util.Context) ([]string, error) {
	result := []string{}
	root, err := util.VanadiumRoot()
	if err != nil {
		return nil, err
	}

	// Collect javascript test results.
	jsDir := filepath.Join(root, "release/javascript/core", "test_out")
	if _, err := os.Stat(jsDir); err == nil {
		fileInfoList, err := ioutil.ReadDir(jsDir)
		if err != nil {
			return nil, fmt.Errorf("ReadDir(%v) failed: %v", jsDir, err)
		}
		for _, fileInfo := range fileInfoList {
			name := fileInfo.Name()
			if strings.HasSuffix(name, "_integration.out") || strings.HasSuffix(name, "_spec.out") {
				result = append(result, filepath.Join(jsDir, name))
			}
		}
	} else {
		if !os.IsNotExist(err) {
			return nil, err
		}
	}

	// Collect non-javascript test results.
	workspaceDir := os.Getenv("WORKSPACE")
	fileInfoList, err := ioutil.ReadDir(workspaceDir)
	if err != nil {
		return nil, fmt.Errorf("ReadDir(%v) failed: %v", workspaceDir, err)
	}
	for _, fileInfo := range fileInfoList {
		fileName := fileInfo.Name()
		if strings.HasPrefix(fileName, "tests_") && strings.HasSuffix(fileName, ".xml") ||
			strings.HasPrefix(fileName, "status_") && strings.HasSuffix(fileName, ".json") {
			result = append(result, filepath.Join(workspaceDir, fileName))
		}
	}
	return result, nil
}

// requireEnv makes sure that the given environment variables are set.
func requireEnv(names []string) error {
	for _, name := range names {
		if os.Getenv(name) == "" {
			return fmt.Errorf("environment variable %q is not set", name)
		}
	}
	return nil
}

// vanadiumPresubmitPoll polls vanadium projects for new patchsets for
// which to run presubmit tests.
func vanadiumPresubmitPoll(ctx *util.Context, testName string, _ ...TestOpt) (_ *TestResult, e error) {
	root, err := util.VanadiumRoot()
	if err != nil {
		return nil, err
	}

	// Initialize the test.
	cleanup, err := initTest(ctx, testName, nil)
	if err != nil {
		return nil, internalTestError{err, "Init"}
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	// Use the "presubmit query" command to poll for new changes.
	logfile := filepath.Join(root, ".presubmit_log")
	args := []string{}
	if ctx.Verbose() {
		args = append(args, "-v")
	}
	args = append(args,
		"-host", jenkinsHost,
		"-netrc", netrcFile,
		"query",
		"-log_file", logfile)
	if err := ctx.Run().Command("presubmit", args...); err != nil {
		return nil, err
	}

	return &TestResult{Status: TestPassed}, nil
}

// vanadiumPresubmitTest runs presubmit tests for a given project specified
// in TEST environment variable.
func vanadiumPresubmitTest(ctx *util.Context, testName string, _ ...TestOpt) (_ *TestResult, e error) {
	if err := requireEnv([]string{"BUILD_NUMBER", "REFS", "PROJECTS", "TEST", "WORKSPACE"}); err != nil {
		return nil, err
	}

	// Initialize the test.
	cleanup, err := initTest(ctx, testName, nil)
	if err != nil {
		return nil, internalTestError{err, "Init"}
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	// Cleanup the test results possibly left behind by the
	// previous presubmit test.
	testResultFiles, err := findTestResultFiles(ctx)
	if err != nil {
		return nil, err
	}
	for _, file := range testResultFiles {
		if err := ctx.Run().RemoveAll(file); err != nil {
			return nil, err
		}
	}

	// Use the "presubmit test" command to run the presubmit test.
	args := []string{}
	if ctx.Verbose() {
		args = append(args, "-v")
	}
	args = append(args,
		"-host", jenkinsHost,
		"-netrc", netrcFile,
		"test",
		"-build_number", os.Getenv("BUILD_NUMBER"),
		"-manifest", "default",
		"-projects", os.Getenv("PROJECTS"),
		"-refs", os.Getenv("REFS"),
		"-test", os.Getenv("TEST"),
	)
	if err := ctx.Run().Command("presubmit", args...); err != nil {
		return nil, err
	}

	// Remove any test result files that are empty.
	testResultFiles, err = findTestResultFiles(ctx)
	if err != nil {
		return nil, err
	}
	for _, file := range testResultFiles {
		fileInfo, err := os.Stat(file)
		if err != nil {
			return nil, err
		}
		if fileInfo.Size() == 0 {
			if err := ctx.Run().RemoveAll(file); err != nil {
				return nil, err
			}
		}
	}

	if testResult, err := generateDummyTestReportFile(ctx, testName); err != nil {
		return nil, err
	} else {
		return testResult, nil
	}
}

// generateDummyTestReportFile generate a dummy test report if
// - the tests we run didn't produce any non-empty files, or
// - the existing test report is invalid, or
// - the existing test report has no test cases.
func generateDummyTestReportFile(ctx *util.Context, testName string) (*TestResult, error) {
	testResultFiles, err := findTestResultFiles(ctx)
	if err != nil {
		return nil, err
	}
	xUnitReportFile := ""
	for _, file := range testResultFiles {
		if strings.HasSuffix(file, ".xml") {
			xUnitReportFile = file
			break
		}
	}
	// No test report.
	if xUnitReportFile == "" {
		workspaceDir := os.Getenv("WORKSPACE")
		dummyFile, perm := filepath.Join(workspaceDir, "tests_dummy.xml"), os.FileMode(0644)
		if err := ctx.Run().WriteFile(dummyFile, []byte(dummyTestResult), perm); err != nil {
			return nil, fmt.Errorf("WriteFile(%v) failed: %v", dummyFile, err)
		}
		return &TestResult{Status: TestPassed}, nil
	}

	// Invalid xUnit file.
	bytes, err := ioutil.ReadFile(xUnitReportFile)
	if err != nil {
		return nil, fmt.Errorf("ReadFile(%s) failed: %v", xUnitReportFile, err)
	}
	var suites testSuites
	if err := xml.Unmarshal(bytes, &suites); err != nil {
		ctx.Run().RemoveAll(xUnitReportFile)
		s := createTestSuiteWithFailure(testName, "Invalid xUnit Report", "Invalid xUnit Report", err.Error(), 0)
		suites := []testSuite{*s}
		if err := createXUnitReport(ctx, testName, suites); err != nil {
			return nil, err
		}
		return &TestResult{Status: TestFailed}, nil
	}

	// No test cases.
	numTestCases := 0
	for _, suite := range suites.Suites {
		numTestCases += len(suite.Cases)
	}
	if numTestCases == 0 {
		ctx.Run().RemoveAll(xUnitReportFile)
		s := createTestSuiteWithFailure(testName, "No Test Cases", "No Test Cases", "", 0)
		suites := []testSuite{*s}
		if err := createXUnitReport(ctx, testName, suites); err != nil {
			return nil, err
		}
		return &TestResult{Status: TestFailed}, nil
	}

	return &TestResult{Status: TestPassed}, nil
}

// vanadiumPresubmitResult runs "presubmit result" command to process and post test resutls.
func vanadiumPresubmitResult(ctx *util.Context, testName string, _ ...TestOpt) (_ *TestResult, e error) {
	if err := requireEnv([]string{"BUILD_NUMBER", "REFS", "PROJECTS", "WORKSPACE"}); err != nil {
		return nil, err
	}

	// Initialize the test.
	cleanup, err := initTest(ctx, testName, nil)
	if err != nil {
		return nil, internalTestError{err, "Init"}
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	// Run "presubmit result".
	args := []string{}
	if ctx.Verbose() {
		args = append(args, "-v")
	}
	args = append(args,
		"-host", jenkinsHost,
		"-netrc", netrcFile,
		"result",
		"-build_number", os.Getenv("BUILD_NUMBER"),
		"-refs", os.Getenv("REFS"),
		"-projects", os.Getenv("PROJECTS"),
	)
	if err := ctx.Run().Command("presubmit", args...); err != nil {
		return nil, err
	}

	return &TestResult{Status: TestPassed}, nil
}
<commit_msg>TBR: lib/testutil/presubmit: switch jenkins master host to the new location.<commit_after>package testutil

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"v.io/tools/lib/collect"
	"v.io/tools/lib/util"
)

var (
	jenkinsHost = "http://localhost:8080/jenkins"
	netrcFile   = filepath.Join(os.Getenv("HOME"), ".netrc")
)

const (
	dummyTestResult = `<?xml version="1.0" encoding="utf-8"?>
<!--
  This file will be used to generate a dummy test results file
  in case the presubmit tests produce no test result files.
-->
<testsuites>
  <testsuite name="NO_TESTS" tests="1" errors="0" failures="0" skip="0">
    <testcase classname="NO_TESTS" name="NO_TESTS" time="0">
    </testcase>
  </testsuite>
</testsuites>
`
)

// findTestResultFiles returns a slice of paths to presubmit test
// results.
func findTestResultFiles(ctx *util.Context) ([]string, error) {
	result := []string{}
	root, err := util.VanadiumRoot()
	if err != nil {
		return nil, err
	}

	// Collect javascript test results.
	jsDir := filepath.Join(root, "release/javascript/core", "test_out")
	if _, err := os.Stat(jsDir); err == nil {
		fileInfoList, err := ioutil.ReadDir(jsDir)
		if err != nil {
			return nil, fmt.Errorf("ReadDir(%v) failed: %v", jsDir, err)
		}
		for _, fileInfo := range fileInfoList {
			name := fileInfo.Name()
			if strings.HasSuffix(name, "_integration.out") || strings.HasSuffix(name, "_spec.out") {
				result = append(result, filepath.Join(jsDir, name))
			}
		}
	} else {
		if !os.IsNotExist(err) {
			return nil, err
		}
	}

	// Collect non-javascript test results.
	workspaceDir := os.Getenv("WORKSPACE")
	fileInfoList, err := ioutil.ReadDir(workspaceDir)
	if err != nil {
		return nil, fmt.Errorf("ReadDir(%v) failed: %v", workspaceDir, err)
	}
	for _, fileInfo := range fileInfoList {
		fileName := fileInfo.Name()
		if strings.HasPrefix(fileName, "tests_") && strings.HasSuffix(fileName, ".xml") ||
			strings.HasPrefix(fileName, "status_") && strings.HasSuffix(fileName, ".json") {
			result = append(result, filepath.Join(workspaceDir, fileName))
		}
	}
	return result, nil
}

// requireEnv makes sure that the given environment variables are set.
func requireEnv(names []string) error {
	for _, name := range names {
		if os.Getenv(name) == "" {
			return fmt.Errorf("environment variable %q is not set", name)
		}
	}
	return nil
}

// vanadiumPresubmitPoll polls vanadium projects for new patchsets for
// which to run presubmit tests.
func vanadiumPresubmitPoll(ctx *util.Context, testName string, _ ...TestOpt) (_ *TestResult, e error) {
	root, err := util.VanadiumRoot()
	if err != nil {
		return nil, err
	}

	// Initialize the test.
	cleanup, err := initTest(ctx, testName, nil)
	if err != nil {
		return nil, internalTestError{err, "Init"}
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	// Use the "presubmit query" command to poll for new changes.
	logfile := filepath.Join(root, ".presubmit_log")
	args := []string{}
	if ctx.Verbose() {
		args = append(args, "-v")
	}
	args = append(args,
		"-host", jenkinsHost,
		"-netrc", netrcFile,
		"query",
		"-log_file", logfile)
	if err := ctx.Run().Command("presubmit", args...); err != nil {
		return nil, err
	}

	return &TestResult{Status: TestPassed}, nil
}

// vanadiumPresubmitTest runs presubmit tests for a given project specified
// in TEST environment variable.
func vanadiumPresubmitTest(ctx *util.Context, testName string, _ ...TestOpt) (_ *TestResult, e error) {
	if err := requireEnv([]string{"BUILD_NUMBER", "REFS", "PROJECTS", "TEST", "WORKSPACE"}); err != nil {
		return nil, err
	}

	// Initialize the test.
	cleanup, err := initTest(ctx, testName, nil)
	if err != nil {
		return nil, internalTestError{err, "Init"}
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	// Cleanup the test results possibly left behind by the
	// previous presubmit test.
	testResultFiles, err := findTestResultFiles(ctx)
	if err != nil {
		return nil, err
	}
	for _, file := range testResultFiles {
		if err := ctx.Run().RemoveAll(file); err != nil {
			return nil, err
		}
	}

	// Use the "presubmit test" command to run the presubmit test.
	args := []string{}
	if ctx.Verbose() {
		args = append(args, "-v")
	}
	args = append(args,
		"-host", jenkinsHost,
		"-netrc", netrcFile,
		"test",
		"-build_number", os.Getenv("BUILD_NUMBER"),
		"-manifest", "default",
		"-projects", os.Getenv("PROJECTS"),
		"-refs", os.Getenv("REFS"),
		"-test", os.Getenv("TEST"),
	)
	if err := ctx.Run().Command("presubmit", args...); err != nil {
		return nil, err
	}

	// Remove any test result files that are empty.
	testResultFiles, err = findTestResultFiles(ctx)
	if err != nil {
		return nil, err
	}
	for _, file := range testResultFiles {
		fileInfo, err := os.Stat(file)
		if err != nil {
			return nil, err
		}
		if fileInfo.Size() == 0 {
			if err := ctx.Run().RemoveAll(file); err != nil {
				return nil, err
			}
		}
	}

	if testResult, err := generateDummyTestReportFile(ctx, testName); err != nil {
		return nil, err
	} else {
		return testResult, nil
	}
}

// generateDummyTestReportFile generate a dummy test report if
// - the tests we run didn't produce any non-empty files, or
// - the existing test report is invalid, or
// - the existing test report has no test cases.
func generateDummyTestReportFile(ctx *util.Context, testName string) (*TestResult, error) {
	testResultFiles, err := findTestResultFiles(ctx)
	if err != nil {
		return nil, err
	}
	xUnitReportFile := ""
	for _, file := range testResultFiles {
		if strings.HasSuffix(file, ".xml") {
			xUnitReportFile = file
			break
		}
	}
	// No test report.
	if xUnitReportFile == "" {
		workspaceDir := os.Getenv("WORKSPACE")
		dummyFile, perm := filepath.Join(workspaceDir, "tests_dummy.xml"), os.FileMode(0644)
		if err := ctx.Run().WriteFile(dummyFile, []byte(dummyTestResult), perm); err != nil {
			return nil, fmt.Errorf("WriteFile(%v) failed: %v", dummyFile, err)
		}
		return &TestResult{Status: TestPassed}, nil
	}

	// Invalid xUnit file.
	bytes, err := ioutil.ReadFile(xUnitReportFile)
	if err != nil {
		return nil, fmt.Errorf("ReadFile(%s) failed: %v", xUnitReportFile, err)
	}
	var suites testSuites
	if err := xml.Unmarshal(bytes, &suites); err != nil {
		ctx.Run().RemoveAll(xUnitReportFile)
		s := createTestSuiteWithFailure(testName, "Invalid xUnit Report", "Invalid xUnit Report", err.Error(), 0)
		suites := []testSuite{*s}
		if err := createXUnitReport(ctx, testName, suites); err != nil {
			return nil, err
		}
		return &TestResult{Status: TestFailed}, nil
	}

	// No test cases.
	numTestCases := 0
	for _, suite := range suites.Suites {
		numTestCases += len(suite.Cases)
	}
	if numTestCases == 0 {
		ctx.Run().RemoveAll(xUnitReportFile)
		s := createTestSuiteWithFailure(testName, "No Test Cases", "No Test Cases", "", 0)
		suites := []testSuite{*s}
		if err := createXUnitReport(ctx, testName, suites); err != nil {
			return nil, err
		}
		return &TestResult{Status: TestFailed}, nil
	}

	return &TestResult{Status: TestPassed}, nil
}

// vanadiumPresubmitResult runs "presubmit result" command to process and post test resutls.
func vanadiumPresubmitResult(ctx *util.Context, testName string, _ ...TestOpt) (_ *TestResult, e error) {
	if err := requireEnv([]string{"BUILD_NUMBER", "REFS", "PROJECTS", "WORKSPACE"}); err != nil {
		return nil, err
	}

	// Initialize the test.
	cleanup, err := initTest(ctx, testName, nil)
	if err != nil {
		return nil, internalTestError{err, "Init"}
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	// Run "presubmit result".
	args := []string{}
	if ctx.Verbose() {
		args = append(args, "-v")
	}
	args = append(args,
		"-host", jenkinsHost,
		"-netrc", netrcFile,
		"result",
		"-build_number", os.Getenv("BUILD_NUMBER"),
		"-refs", os.Getenv("REFS"),
		"-projects", os.Getenv("PROJECTS"),
	)
	if err := ctx.Run().Command("presubmit", args...); err != nil {
		return nil, err
	}

	return &TestResult{Status: TestPassed}, nil
}
<|endoftext|>


<commit_before>package worker

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/MG-RAST/AWE/lib/conf"
	"github.com/MG-RAST/AWE/lib/core"
	e "github.com/MG-RAST/AWE/lib/errors"
	"github.com/MG-RAST/AWE/lib/httpclient"
	"github.com/MG-RAST/AWE/lib/logger"
	"github.com/MG-RAST/AWE/lib/logger/event"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type HeartbeatResponse struct {
	Code int        `bson:"status" json:"status"`
	Data core.HBmsg `bson:"data" json:"data"`
	Errs []string   `bson:"error" json:"error"`
}

type ClientResponse struct {
	Code int         `bson:"status" json:"status"`
	Data core.Client `bson:"data" json:"data"`
	Errs []string    `bson:"error" json:"error"`
}

func heartBeater(control chan int) {
	for {
		time.Sleep(10 * time.Second)
		SendHeartBeat()
	}
	control <- 2 //we are ending
}

//client sends heartbeat to server to maintain active status and re-register when needed
func SendHeartBeat() {
	hbmsg, err := heartbeating(conf.SERVER_URL, core.Self.Id)
	if err != nil {
		if err.Error() == e.ClientNotFound {
			ReRegisterWithSelf(conf.SERVER_URL)
		}
	}
	//handle requested ops from the server
	for op, objs := range hbmsg {
		if op == "discard" { //discard suspended workunits
			suspendedworks := strings.Split(objs, ",")
			for _, work := range suspendedworks {
				DiscardWorkunit(work)
			}
		} else if op == "restart" {
			RestartClient()
		} else if op == "stop" {
			StopClient()
		} else if op == "clean" {
			CleanDisk()
		}
	}
}

func heartbeating(host string, clientid string) (msg core.HBmsg, err error) {
	response := new(HeartbeatResponse)
	targeturl := fmt.Sprintf("%s/client/%s?heartbeat", host, clientid)
	//res, err := http.Get(targeturl)
	res, err := httpclient.Get(targeturl, httpclient.Header{}, nil, nil)
	logger.Debug(3, fmt.Sprintf("client %s sent a heartbeat to %s", host, clientid))
	if err != nil {
		return
	}
	defer res.Body.Close()
	jsonstream, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}
	if err = json.Unmarshal(jsonstream, response); err == nil {
		if len(response.Errs) > 0 {
			return msg, errors.New(strings.Join(response.Errs, ","))
		}
		return response.Data, nil
	}
	return
}

func RegisterWithProfile(host string, profile *core.Client) (client *core.Client, err error) {
	profile_jsonstream, err := json.Marshal(profile)
	profile_path := conf.DATA_PATH + "/clientprofile.json"
	ioutil.WriteFile(profile_path, []byte(profile_jsonstream), 0644)

	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)
	fileWriter, err := bodyWriter.CreateFormFile("profile", profile_path)
	if err != nil {
		return nil, err
	}
	fh, err := os.Open(profile_path)
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(fileWriter, fh)
	if err != nil {
		return nil, err
	}
	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()
	targetUrl := host + "/client"

	resp, err := http.Post(targetUrl, contentType, bodyBuf)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	jsonstream, err := ioutil.ReadAll(resp.Body)

	response := new(ClientResponse)
	if err = json.Unmarshal(jsonstream, response); err != nil {
		return nil, errors.New("fail to unmashal response:" + string(jsonstream))
	}
	if len(response.Errs) > 0 {
		return nil, errors.New(strings.Join(response.Errs, ","))
	}
	client = &response.Data
	return
}

func RegisterWithAuth(host string, profile *core.Client) (client *core.Client, err error) {
	if conf.CLIENT_USERNAME == "public" {
		fmt.Println("client username and password not configured, register as a public user (can only access public data)")
	}

	profile_jsonstream, err := json.Marshal(profile)
	profile_path := conf.DATA_PATH + "/clientprofile.json"
	ioutil.WriteFile(profile_path, []byte(profile_jsonstream), 0644)

	form := httpclient.NewForm()
	form.AddFile("profile", profile_path)
	if err := form.Create(); err != nil {
		return nil, err
	}
	headers := httpclient.Header{
		"Content-Type":   []string{form.ContentType},
		"Content-Length": []string{strconv.FormatInt(form.Length, 10)},
	}
	user := httpclient.GetUserByBasicAuth(conf.CLIENT_USERNAME, conf.CLIENT_PASSWORD)
	targetUrl := host + "/client"

	resp, err := httpclient.Post(targetUrl, headers, form.Reader, user)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	jsonstream, err := ioutil.ReadAll(resp.Body)
	response := new(ClientResponse)
	if err = json.Unmarshal(jsonstream, response); err != nil {
		return nil, errors.New("fail to unmashal response:" + string(jsonstream))
	}
	if len(response.Errs) > 0 {
		return nil, errors.New(strings.Join(response.Errs, ","))
	}
	client = &response.Data
	return
}

func ReRegisterWithSelf(host string) (client *core.Client, err error) {
	fmt.Printf("lost contact with server, try to re-register\n")
	client, err = RegisterWithAuth(host, core.Self)
	if err != nil {
		logger.Error("Error: fail to re-register, clientid=" + core.Self.Id)
		fmt.Printf("failed to re-register\n")
	} else {
		logger.Event(event.CLIENT_AUTO_REREGI, "clientid="+core.Self.Id)
		fmt.Printf("re-register successfully\n")
	}
	return
}

func ComposeProfile() (profile *core.Client, err error) {
	profile = new(core.Client)
	profile.Name = conf.CLIENT_NAME
	profile.Group = conf.CLIENT_GROUP
	profile.CPUs = runtime.NumCPU()
	profile.User = conf.CLIENT_USERNAME
	profile.Domain = conf.CLIENT_DOMAIN
	profile.Version = conf.VERSION

	//app list
	profile.Apps = []string{}
	if conf.SUPPORTED_APPS != "" { //apps configured in .cfg
		apps := strings.Split(conf.SUPPORTED_APPS, ",")
		for _, item := range apps {
			profile.Apps = append(profile.Apps, item)
		}
	} else { //apps not configured in .cfg, check the executables in APP_PATH)
		if files, err := ioutil.ReadDir(conf.APP_PATH); err == nil {
			for _, item := range files {
				profile.Apps = append(profile.Apps, item.Name())
			}
		}
	}

	if addrs, err := net.InterfaceAddrs(); err == nil {
		for _, a := range addrs {
			if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && len(strings.Split(ipnet.IP.String(), ".")) == 4 {
				profile.Host = ipnet.IP.String()
				break
			}
		}
	}

	if len(conf.OPENSTACK_METADATA_URL) > 7 { // longer than "http://"

		logger.Debug(1, fmt.Sprintf("openstack_metadata_url=%s, getting instance_id and instance_type...", conf.OPENSTACK_METADATA_URL))

		// read all values: for i in `curl http://169.254.169.254/1.0/meta-data/` ; do echo ${i}: `curl -s http://169.254.169.254/1.0/meta-data/${i}` ; done
		instance_hostname, err := getMetaDataField("hostname")
		if err == nil {
			instance_hostname = strings.TrimSuffix(instance_hostname, ".novalocal")
			profile.Name = instance_hostname
		}
		instance_id, err := getMetaDataField("instance-id")
		if err == nil {
			profile.InstanceId = instance_id
		}
		instance_type, err := getMetaDataField("instance-type")
		if err == nil {
			profile.InstanceType = instance_type
		}
		local_ipv4, err := getMetaDataField("local-ipv4")
		if err == nil {
			profile.Host = local_ipv4
		}

		//for i := 0; i < 3; i++ {
		//	profile.InstanceId, _ = getInstanceId()
		//	if profile.InstanceId != "" {
		//		break
		//	}
		//}
		//for i := 0; i < 3; i++ {
		//	profile.InstanceType, _ = getInstanceType()
		//	if profile.InstanceType != "" {
		//		break
		//	}
		//}
	}

	if core.Service == "proxy" {
		profile.Proxy = true
	}
	return
}

func DiscardWorkunit(id string) (err error) {
	//fmt.Printf("try to discard workunit %s\n", id)
	if stage, ok := workmap[id]; ok {
		if stage == ID_WORKER {
			chankill <- true
		}
		workmap[id] = ID_DISCARDED
	}
	return
}

func RestartClient() (err error) {
	//fmt.Printf("try to restart client\n")
	//to-do: implementation here
	return
}

func StopClient() (err error) {
	fmt.Printf("client deleted, exiting...\n")
	os.Exit(0)
	return
}

func CleanDisk() (err error) {
	//fmt.Printf("try to clean disk space\n")
	//to-do: implementation here
	return
}

func getMetaDataField(field string) (result string, err error) {
	var url = fmt.Sprintf("%s/%s", conf.OPENSTACK_METADATA_URL, field) // TODO this is not OPENSTACK, this is EC2
	logger.Debug(1, fmt.Sprintf("url=%s", url))
MYFOR:
	for i := 0; i < 3; i++ {
		var res *http.Response
		c := make(chan bool, 1)
		go func() {
			res, err = http.Get(url)
			c <- true //we are ending
		}()
		select {
		case <-c:
			//go ahead
		case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
			err = errors.New("warning: " + url + " timeout")
		}
		defer res.Body.Close()
		if err != nil {
			//return "", err
			logger.Error("warning: " + url + " " + err.Error())
			continue
		}

		bodybytes, err := ioutil.ReadAll(res.Body)
		result = string(bodybytes[:])
		if err != nil {
			logger.Error(fmt.Sprintf("warning: (iteration=%d) %s \"%s\"", i, url, err.Error()))
			continue
		} else if result == "" {
			logger.Error(fmt.Sprintf("warning: (iteration=%d) %s empty result", i, url))
			continue
		}

		break

	}

	if err != nil {
		return "", err
	}

	if result == "" {
		return "", errors.New(fmt.Sprintf("metadata result empty, %s", url))
	}
	//fmt.Printf("Intance Metadata %s => \"%s\"\n", url, result)
	logger.Debug(1, fmt.Sprintf("Intance Metadata %s => \"%s\"", url, result))
	return
}

// TODO deprecated by getMetaDataField !?
func getInstanceId() (instance_id string, err error) {
	var instance_id_url = fmt.Sprintf("%s/instance-id", conf.OPENSTACK_METADATA_URL)
	var res *http.Response
	c := make(chan bool, 1)
	go func() {
		res, err = http.Get(instance_id_url)
		c <- true //we are ending
	}()
	select {
	case <-c:
		//go ahead
	case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
		return "", errors.New("timeout")
	}
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	bodybytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	instance_id = string(bodybytes[:])
	fmt.Printf("instance_id=%s\n", instance_id)
	return
}

// TODO deprecated by getMetaDataField !?
func getInstanceType() (instance_type string, err error) {
	var instance_type_url = fmt.Sprintf("%s/instance-type", conf.OPENSTACK_METADATA_URL)
	var res *http.Response
	c := make(chan bool, 1)
	go func() {
		res, err = http.Get(instance_type_url)
		c <- true //we are ending
	}()
	select {
	case <-c:
		//go ahead
	case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
		return "", errors.New("timeout")
	}
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	bodybytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	instance_type = string(bodybytes[:])
	fmt.Printf("instance_type=%s\n", instance_type)
	return
}
<commit_msg>loop name removed<commit_after>package worker

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/MG-RAST/AWE/lib/conf"
	"github.com/MG-RAST/AWE/lib/core"
	e "github.com/MG-RAST/AWE/lib/errors"
	"github.com/MG-RAST/AWE/lib/httpclient"
	"github.com/MG-RAST/AWE/lib/logger"
	"github.com/MG-RAST/AWE/lib/logger/event"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type HeartbeatResponse struct {
	Code int        `bson:"status" json:"status"`
	Data core.HBmsg `bson:"data" json:"data"`
	Errs []string   `bson:"error" json:"error"`
}

type ClientResponse struct {
	Code int         `bson:"status" json:"status"`
	Data core.Client `bson:"data" json:"data"`
	Errs []string    `bson:"error" json:"error"`
}

func heartBeater(control chan int) {
	for {
		time.Sleep(10 * time.Second)
		SendHeartBeat()
	}
	control <- 2 //we are ending
}

//client sends heartbeat to server to maintain active status and re-register when needed
func SendHeartBeat() {
	hbmsg, err := heartbeating(conf.SERVER_URL, core.Self.Id)
	if err != nil {
		if err.Error() == e.ClientNotFound {
			ReRegisterWithSelf(conf.SERVER_URL)
		}
	}
	//handle requested ops from the server
	for op, objs := range hbmsg {
		if op == "discard" { //discard suspended workunits
			suspendedworks := strings.Split(objs, ",")
			for _, work := range suspendedworks {
				DiscardWorkunit(work)
			}
		} else if op == "restart" {
			RestartClient()
		} else if op == "stop" {
			StopClient()
		} else if op == "clean" {
			CleanDisk()
		}
	}
}

func heartbeating(host string, clientid string) (msg core.HBmsg, err error) {
	response := new(HeartbeatResponse)
	targeturl := fmt.Sprintf("%s/client/%s?heartbeat", host, clientid)
	//res, err := http.Get(targeturl)
	res, err := httpclient.Get(targeturl, httpclient.Header{}, nil, nil)
	logger.Debug(3, fmt.Sprintf("client %s sent a heartbeat to %s", host, clientid))
	if err != nil {
		return
	}
	defer res.Body.Close()
	jsonstream, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}
	if err = json.Unmarshal(jsonstream, response); err == nil {
		if len(response.Errs) > 0 {
			return msg, errors.New(strings.Join(response.Errs, ","))
		}
		return response.Data, nil
	}
	return
}

func RegisterWithProfile(host string, profile *core.Client) (client *core.Client, err error) {
	profile_jsonstream, err := json.Marshal(profile)
	profile_path := conf.DATA_PATH + "/clientprofile.json"
	ioutil.WriteFile(profile_path, []byte(profile_jsonstream), 0644)

	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)
	fileWriter, err := bodyWriter.CreateFormFile("profile", profile_path)
	if err != nil {
		return nil, err
	}
	fh, err := os.Open(profile_path)
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(fileWriter, fh)
	if err != nil {
		return nil, err
	}
	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()
	targetUrl := host + "/client"

	resp, err := http.Post(targetUrl, contentType, bodyBuf)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	jsonstream, err := ioutil.ReadAll(resp.Body)

	response := new(ClientResponse)
	if err = json.Unmarshal(jsonstream, response); err != nil {
		return nil, errors.New("fail to unmashal response:" + string(jsonstream))
	}
	if len(response.Errs) > 0 {
		return nil, errors.New(strings.Join(response.Errs, ","))
	}
	client = &response.Data
	return
}

func RegisterWithAuth(host string, profile *core.Client) (client *core.Client, err error) {
	if conf.CLIENT_USERNAME == "public" {
		fmt.Println("client username and password not configured, register as a public user (can only access public data)")
	}

	profile_jsonstream, err := json.Marshal(profile)
	profile_path := conf.DATA_PATH + "/clientprofile.json"
	ioutil.WriteFile(profile_path, []byte(profile_jsonstream), 0644)

	form := httpclient.NewForm()
	form.AddFile("profile", profile_path)
	if err := form.Create(); err != nil {
		return nil, err
	}
	headers := httpclient.Header{
		"Content-Type":   []string{form.ContentType},
		"Content-Length": []string{strconv.FormatInt(form.Length, 10)},
	}
	user := httpclient.GetUserByBasicAuth(conf.CLIENT_USERNAME, conf.CLIENT_PASSWORD)
	targetUrl := host + "/client"

	resp, err := httpclient.Post(targetUrl, headers, form.Reader, user)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	jsonstream, err := ioutil.ReadAll(resp.Body)
	response := new(ClientResponse)
	if err = json.Unmarshal(jsonstream, response); err != nil {
		return nil, errors.New("fail to unmashal response:" + string(jsonstream))
	}
	if len(response.Errs) > 0 {
		return nil, errors.New(strings.Join(response.Errs, ","))
	}
	client = &response.Data
	return
}

func ReRegisterWithSelf(host string) (client *core.Client, err error) {
	fmt.Printf("lost contact with server, try to re-register\n")
	client, err = RegisterWithAuth(host, core.Self)
	if err != nil {
		logger.Error("Error: fail to re-register, clientid=" + core.Self.Id)
		fmt.Printf("failed to re-register\n")
	} else {
		logger.Event(event.CLIENT_AUTO_REREGI, "clientid="+core.Self.Id)
		fmt.Printf("re-register successfully\n")
	}
	return
}

func ComposeProfile() (profile *core.Client, err error) {
	profile = new(core.Client)
	profile.Name = conf.CLIENT_NAME
	profile.Group = conf.CLIENT_GROUP
	profile.CPUs = runtime.NumCPU()
	profile.User = conf.CLIENT_USERNAME
	profile.Domain = conf.CLIENT_DOMAIN
	profile.Version = conf.VERSION

	//app list
	profile.Apps = []string{}
	if conf.SUPPORTED_APPS != "" { //apps configured in .cfg
		apps := strings.Split(conf.SUPPORTED_APPS, ",")
		for _, item := range apps {
			profile.Apps = append(profile.Apps, item)
		}
	} else { //apps not configured in .cfg, check the executables in APP_PATH)
		if files, err := ioutil.ReadDir(conf.APP_PATH); err == nil {
			for _, item := range files {
				profile.Apps = append(profile.Apps, item.Name())
			}
		}
	}

	if addrs, err := net.InterfaceAddrs(); err == nil {
		for _, a := range addrs {
			if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && len(strings.Split(ipnet.IP.String(), ".")) == 4 {
				profile.Host = ipnet.IP.String()
				break
			}
		}
	}

	if len(conf.OPENSTACK_METADATA_URL) > 7 { // longer than "http://"

		logger.Debug(1, fmt.Sprintf("openstack_metadata_url=%s, getting instance_id and instance_type...", conf.OPENSTACK_METADATA_URL))

		// read all values: for i in `curl http://169.254.169.254/1.0/meta-data/` ; do echo ${i}: `curl -s http://169.254.169.254/1.0/meta-data/${i}` ; done
		instance_hostname, err := getMetaDataField("hostname")
		if err == nil {
			instance_hostname = strings.TrimSuffix(instance_hostname, ".novalocal")
			profile.Name = instance_hostname
		}
		instance_id, err := getMetaDataField("instance-id")
		if err == nil {
			profile.InstanceId = instance_id
		}
		instance_type, err := getMetaDataField("instance-type")
		if err == nil {
			profile.InstanceType = instance_type
		}
		local_ipv4, err := getMetaDataField("local-ipv4")
		if err == nil {
			profile.Host = local_ipv4
		}

		//for i := 0; i < 3; i++ {
		//	profile.InstanceId, _ = getInstanceId()
		//	if profile.InstanceId != "" {
		//		break
		//	}
		//}
		//for i := 0; i < 3; i++ {
		//	profile.InstanceType, _ = getInstanceType()
		//	if profile.InstanceType != "" {
		//		break
		//	}
		//}
	}

	if core.Service == "proxy" {
		profile.Proxy = true
	}
	return
}

func DiscardWorkunit(id string) (err error) {
	//fmt.Printf("try to discard workunit %s\n", id)
	if stage, ok := workmap[id]; ok {
		if stage == ID_WORKER {
			chankill <- true
		}
		workmap[id] = ID_DISCARDED
	}
	return
}

func RestartClient() (err error) {
	//fmt.Printf("try to restart client\n")
	//to-do: implementation here
	return
}

func StopClient() (err error) {
	fmt.Printf("client deleted, exiting...\n")
	os.Exit(0)
	return
}

func CleanDisk() (err error) {
	//fmt.Printf("try to clean disk space\n")
	//to-do: implementation here
	return
}

func getMetaDataField(field string) (result string, err error) {
	var url = fmt.Sprintf("%s/%s", conf.OPENSTACK_METADATA_URL, field) // TODO this is not OPENSTACK, this is EC2
	logger.Debug(1, fmt.Sprintf("url=%s", url))

	for i := 0; i < 3; i++ {
		var res *http.Response
		c := make(chan bool, 1)
		go func() {
			res, err = http.Get(url)
			c <- true //we are ending
		}()
		select {
		case <-c:
			//go ahead
		case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
			err = errors.New("warning: " + url + " timeout")
		}
		defer res.Body.Close()
		if err != nil {
			//return "", err
			logger.Error("warning: " + url + " " + err.Error())
			continue
		}

		bodybytes, err := ioutil.ReadAll(res.Body)
		result = string(bodybytes[:])
		if err != nil {
			logger.Error(fmt.Sprintf("warning: (iteration=%d) %s \"%s\"", i, url, err.Error()))
			continue
		} else if result == "" {
			logger.Error(fmt.Sprintf("warning: (iteration=%d) %s empty result", i, url))
			continue
		}

		break

	}

	if err != nil {
		return "", err
	}

	if result == "" {
		return "", errors.New(fmt.Sprintf("metadata result empty, %s", url))
	}
	//fmt.Printf("Intance Metadata %s => \"%s\"\n", url, result)
	logger.Debug(1, fmt.Sprintf("Intance Metadata %s => \"%s\"", url, result))
	return
}

// TODO deprecated by getMetaDataField !?
func getInstanceId() (instance_id string, err error) {
	var instance_id_url = fmt.Sprintf("%s/instance-id", conf.OPENSTACK_METADATA_URL)
	var res *http.Response
	c := make(chan bool, 1)
	go func() {
		res, err = http.Get(instance_id_url)
		c <- true //we are ending
	}()
	select {
	case <-c:
		//go ahead
	case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
		return "", errors.New("timeout")
	}
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	bodybytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	instance_id = string(bodybytes[:])
	fmt.Printf("instance_id=%s\n", instance_id)
	return
}

// TODO deprecated by getMetaDataField !?
func getInstanceType() (instance_type string, err error) {
	var instance_type_url = fmt.Sprintf("%s/instance-type", conf.OPENSTACK_METADATA_URL)
	var res *http.Response
	c := make(chan bool, 1)
	go func() {
		res, err = http.Get(instance_type_url)
		c <- true //we are ending
	}()
	select {
	case <-c:
		//go ahead
	case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
		return "", errors.New("timeout")
	}
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	bodybytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	instance_type = string(bodybytes[:])
	fmt.Printf("instance_type=%s\n", instance_type)
	return
}
<|endoftext|>


<commit_before>package main

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

// Although other tests besides those in this file require this env var, this init() func will cover all tests.
func init() {
	if os.Getenv("GITHUB_OAUTH_TOKEN") == "" {
		fmt.Println("ERROR: These tests require that env var GITHUB_OAUTH_TOKEN be set to a GitHub Personal Access Token.")
		fmt.Println("See the tests cases to see which GitHub repos the oAuth token needs access to.")
		os.Exit(1)
	}
}

func TestDownloadGitTagZipFile(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	enterpriseGitHubExample := GitHubInstance{
		BaseUrl: "github.acme.com",
		ApiUrl: "github.acme.com/api/v3",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		gitTag      string
		githubToken string
	}{
		{publicGitHub, "gruntwork-io", "fetch-test-public", "v0.0.1", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-private", "v0.0.2", os.Getenv("GITHUB_OAUTH_TOKEN")},
		{enterpriseGitHubExample, "temp-internal-org", "bash-commons", "v0.0.4", os.Getenv("GITHUB_OAUTH_TOKEN")},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			GitTag: tc.gitTag,
		}

		zipFilePath, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)

		defer os.RemoveAll(zipFilePath)

		// We don't have a running instance of GitHub Enterprise against which to validate tests as we do for GitHub public,
		// so this test will only validate that fetch attempted to download from the expected URL. The download itself
		// will fail.

		githubEnterpriseDownloadUrl := fmt.Sprintf("https://%s/repos/%s/%s/zipball/%s", tc.instance.ApiUrl, tc.repoOwner, tc.repoName, tc.gitTag)
		githubEnterpriseErrorMessage := fmt.Sprintf("Get %s: dial tcp: lookup %s: no such host", githubEnterpriseDownloadUrl, tc.instance.BaseUrl)

		// TODO: The awkwardness of this test makes it clear that a better structure for this program would be to refactor
		// the downloadGithubZipFile() function to a function called downloadGithubFile() that would accept a URL as a
		// param. We could then test explicitly that the URL is as expected, which would make GitHub Enterprise test cases
		// simpler to handle.

		if err != nil && strings.Contains(err.Error(), "no such host") {
			if strings.Contains(err.Error(), githubEnterpriseErrorMessage) {
				t.Logf("Found expected download URL %s. Download itself failed as expected because no GitHub Enterprise instance exists at the given URL.", githubEnterpriseDownloadUrl)
				t.SkipNow()
			} else {
				t.Fatalf("Attempted to download from URL other than the expected download URL of %s. Full error: %s", githubEnterpriseDownloadUrl, err.Error())
			}
		}

		if err != nil {
			t.Fatalf("Failed to download file: %s", err)
		}

		if _, err := os.Stat(zipFilePath); os.IsNotExist(err) {
			t.Fatalf("Downloaded file doesn't exist at the expected path of %s", zipFilePath)
		}
	}
}

func TestDownloadGitBranchZipFile(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		branchName  string
		githubToken string
	}{
		{publicGitHub, "gruntwork-io", "fetch-test-public", "sample-branch", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-private", "sample-branch", os.Getenv("GITHUB_OAUTH_TOKEN")},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			BranchName: tc.branchName,
		}

		zipFilePath, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)
		defer os.RemoveAll(zipFilePath)
		if err != nil {
			t.Fatalf("Failed to download file: %s", err)
		}

		if _, err := os.Stat(zipFilePath); os.IsNotExist(err) {
			t.Fatalf("Downloaded file doesn't exist at the expected path of %s", zipFilePath)
		}
	}
}

func TestDownloadBadGitBranchZipFile(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		branchName  string
		githubToken string
	}{
		{publicGitHub, "gruntwork-io", "fetch-test-public", "branch-that-doesnt-exist", ""},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			BranchName: tc.branchName,
		}

		zipFilePath, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)
		defer os.RemoveAll(zipFilePath)
		if err == nil {
			t.Fatalf("Expected that attempt to download repo %s/%s for branch \"%s\" would fail, but received no error.", tc.repoOwner, tc.repoName, tc.branchName)
		}
	}
}

func TestDownloadGitCommitFile(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		commitSha   string
		githubToken string
	}{
		{publicGitHub, "gruntwork-io", "fetch-test-public", "d2de34edb4c6564e0674b3f390b3b1fb0468183a", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-public", "57752e7f1df0acbd3c1e61545d5c4d0e87699d84", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-public", "f32a08313e30f116a1f5617b8b68c11f1c1dbb61", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-private", "676cfb92b54d33538c756c7a9479bfc3f6b44de2", os.Getenv("GITHUB_OAUTH_TOKEN")},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			CommitSha: tc.commitSha,
		}

		zipFilePath, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)
		defer os.RemoveAll(zipFilePath)
		if err != nil {
			t.Fatalf("Failed to download file: %s", err)
		}

		if _, err := os.Stat(zipFilePath); os.IsNotExist(err) {
			t.Fatalf("Downloaded file doesn't exist at the expected path of %s", zipFilePath)
		}
	}
}

func TestDownloadBadGitCommitFile(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		commitSha   string
		githubToken string
	}{
		{publicGitHub, "gruntwork-io", "fetch-test-public", "hello-world", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-public", "i-am-a-non-existent-commit", ""},
		// remove a single letter from the beginning of an otherwise legit commit sha
		// interestingly, through testing I found that GitHub will attempt to find the right commit sha if you
		// truncate the end of it.
		{publicGitHub, "gruntwork-io", "fetch-test-public", "7752e7f1df0acbd3c1e61545d5c4d0e87699d84", ""},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			CommitSha: tc.commitSha,
		}

		zipFilePath, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)
		defer os.RemoveAll(zipFilePath)
		if err == nil {
			t.Fatalf("Expected that attempt to download repo %s/%s at commmit sha \"%s\" would fail, but received no error.", tc.repoOwner, tc.repoName, tc.commitSha)
		}
	}
}

func TestDownloadZipFileWithBadRepoValues(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		gitTag      string
		githubToken string
	}{
		{publicGitHub, "https://github.com/gruntwork-io/fetch-test-public/archive/does-not-exist.zip", "MyNameIsWhat", "x.y.z", ""},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			GitTag: tc.gitTag,
		}

		_, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)
		if err == nil && err.errorCode != 500 {
			t.Fatalf("Expected error for bad repo values: %s/%s:%s", tc.repoOwner, tc.repoName, tc.gitTag)
		}
	}
}

func TestExtractFiles(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 		  GitHubInstance
		localFilePath     string
		filePathToExtract string
		expectedNumFiles  int
		nonemptyFiles     []string
	}{
		{publicGitHub, "test-fixtures/fetch-test-public-0.0.1.zip", "/", 1, nil},
		{publicGitHub, "test-fixtures/fetch-test-public-0.0.2.zip", "/", 2, nil},
		{publicGitHub, "test-fixtures/fetch-test-public-0.0.3.zip", "/", 4, []string{"/README.md"} },
		{publicGitHub, "test-fixtures/fetch-test-public-0.0.3.zip", "/folder", 2, nil},
	}

	for _, tc := range cases {
		// Create a temp directory
		tempDir, err := ioutil.TempDir("", "")
		if err != nil {
			t.Fatalf("Failed to create temp directory: %s", err)
		}
		defer os.RemoveAll(tempDir)

		err = extractFiles(tc.localFilePath, tc.filePathToExtract, tempDir)
		if err != nil {
			t.Fatalf("Failed to extract files: %s", err)
		}

		// Count the number of files in the directory
		var numFiles int
		filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
			if ! info.IsDir() {
				numFiles++
			}
			return nil
		})

		if (numFiles != tc.expectedNumFiles) {
			t.Fatalf("While extracting %s, expected to find %d file(s), but found %d. Local path = %s", tc.localFilePath, tc.expectedNumFiles, numFiles, tempDir)
		}

		// Ensure that files declared to be non-empty are in fact non-empty
		filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
			relativeFilename := strings.TrimPrefix(path, tempDir)

			if ! info.IsDir() && stringInSlice(relativeFilename, tc.nonemptyFiles) {
				if info.Size() == 0 {
					t.Fatalf("Expected %s in %s to have non-zero file size, but found file size = %d.\n", relativeFilename, tc.localFilePath, info.Size())
				}
			}
			return nil
		})

	}
}

// Return ture if the given slice contains the given string
func stringInSlice(s string, slice []string) bool {
	for _, val := range slice {
		if val == s {
			return true
		}
	}
	return false
}
<commit_msg>Make GitHub Enterprise download test less brittle.<commit_after>package main

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

// Although other tests besides those in this file require this env var, this init() func will cover all tests.
func init() {
	if os.Getenv("GITHUB_OAUTH_TOKEN") == "" {
		fmt.Println("ERROR: These tests require that env var GITHUB_OAUTH_TOKEN be set to a GitHub Personal Access Token.")
		fmt.Println("See the tests cases to see which GitHub repos the oAuth token needs access to.")
		os.Exit(1)
	}
}

func TestDownloadGitTagZipFile(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	enterpriseGitHubExample := GitHubInstance{
		BaseUrl: "github.acme.com",
		ApiUrl: "github.acme.com/api/v3",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		gitTag      string
		githubToken string
	}{
		{publicGitHub, "gruntwork-io", "fetch-test-public", "v0.0.1", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-private", "v0.0.2", os.Getenv("GITHUB_OAUTH_TOKEN")},
		{enterpriseGitHubExample, "temp-internal-org", "bash-commons", "v0.0.4", os.Getenv("GITHUB_OAUTH_TOKEN")},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			GitTag: tc.gitTag,
		}

		zipFilePath, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)

		defer os.RemoveAll(zipFilePath)

		// We don't have a running instance of GitHub Enterprise against which to validate tests as we do for GitHub public,
		// so this test will only validate that fetch attempted to download from the expected URL. The download itself
		// will fail.

		githubEnterpriseDownloadUrl := fmt.Sprintf("https://%s/repos/%s/%s/zipball/%s", tc.instance.ApiUrl, tc.repoOwner, tc.repoName, tc.gitTag)

		// TODO: The awkwardness of this test makes it clear that a better structure for this program would be to refactor
		// the downloadGithubZipFile() function to a function called downloadGithubFile() that would accept a URL as a
		// param. We could then test explicitly that the URL is as expected, which would make GitHub Enterprise test cases
		// simpler to handle.

		if err != nil && strings.Contains(err.Error(), "no such host") {
			if strings.Contains(err.Error(), githubEnterpriseDownloadUrl) {
				t.Logf("Found expected download URL %s. Download itself failed as expected because no GitHub Enterprise instance exists at the given URL.", githubEnterpriseDownloadUrl)
				t.SkipNow()
			} else {
				t.Fatalf("Attempted to download from URL other than the expected download URL of %s. Full error: %s", githubEnterpriseDownloadUrl, err.Error())
			}
		}

		if err != nil {
			t.Fatalf("Failed to download file: %s", err)
		}

		if _, err := os.Stat(zipFilePath); os.IsNotExist(err) {
			t.Fatalf("Downloaded file doesn't exist at the expected path of %s", zipFilePath)
		}
	}
}

func TestDownloadGitBranchZipFile(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		branchName  string
		githubToken string
	}{
		{publicGitHub, "gruntwork-io", "fetch-test-public", "sample-branch", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-private", "sample-branch", os.Getenv("GITHUB_OAUTH_TOKEN")},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			BranchName: tc.branchName,
		}

		zipFilePath, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)
		defer os.RemoveAll(zipFilePath)
		if err != nil {
			t.Fatalf("Failed to download file: %s", err)
		}

		if _, err := os.Stat(zipFilePath); os.IsNotExist(err) {
			t.Fatalf("Downloaded file doesn't exist at the expected path of %s", zipFilePath)
		}
	}
}

func TestDownloadBadGitBranchZipFile(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		branchName  string
		githubToken string
	}{
		{publicGitHub, "gruntwork-io", "fetch-test-public", "branch-that-doesnt-exist", ""},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			BranchName: tc.branchName,
		}

		zipFilePath, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)
		defer os.RemoveAll(zipFilePath)
		if err == nil {
			t.Fatalf("Expected that attempt to download repo %s/%s for branch \"%s\" would fail, but received no error.", tc.repoOwner, tc.repoName, tc.branchName)
		}
	}
}

func TestDownloadGitCommitFile(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		commitSha   string
		githubToken string
	}{
		{publicGitHub, "gruntwork-io", "fetch-test-public", "d2de34edb4c6564e0674b3f390b3b1fb0468183a", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-public", "57752e7f1df0acbd3c1e61545d5c4d0e87699d84", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-public", "f32a08313e30f116a1f5617b8b68c11f1c1dbb61", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-private", "676cfb92b54d33538c756c7a9479bfc3f6b44de2", os.Getenv("GITHUB_OAUTH_TOKEN")},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			CommitSha: tc.commitSha,
		}

		zipFilePath, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)
		defer os.RemoveAll(zipFilePath)
		if err != nil {
			t.Fatalf("Failed to download file: %s", err)
		}

		if _, err := os.Stat(zipFilePath); os.IsNotExist(err) {
			t.Fatalf("Downloaded file doesn't exist at the expected path of %s", zipFilePath)
		}
	}
}

func TestDownloadBadGitCommitFile(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		commitSha   string
		githubToken string
	}{
		{publicGitHub, "gruntwork-io", "fetch-test-public", "hello-world", ""},
		{publicGitHub, "gruntwork-io", "fetch-test-public", "i-am-a-non-existent-commit", ""},
		// remove a single letter from the beginning of an otherwise legit commit sha
		// interestingly, through testing I found that GitHub will attempt to find the right commit sha if you
		// truncate the end of it.
		{publicGitHub, "gruntwork-io", "fetch-test-public", "7752e7f1df0acbd3c1e61545d5c4d0e87699d84", ""},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			CommitSha: tc.commitSha,
		}

		zipFilePath, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)
		defer os.RemoveAll(zipFilePath)
		if err == nil {
			t.Fatalf("Expected that attempt to download repo %s/%s at commmit sha \"%s\" would fail, but received no error.", tc.repoOwner, tc.repoName, tc.commitSha)
		}
	}
}

func TestDownloadZipFileWithBadRepoValues(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 	GitHubInstance
		repoOwner   string
		repoName    string
		gitTag      string
		githubToken string
	}{
		{publicGitHub, "https://github.com/gruntwork-io/fetch-test-public/archive/does-not-exist.zip", "MyNameIsWhat", "x.y.z", ""},
	}

	for _, tc := range cases {
		gitHubCommit := GitHubCommit{
			Repo: GitHubRepo{
				Owner: tc.repoOwner,
				Name: tc.repoName,
			},
			GitTag: tc.gitTag,
		}

		_, err := downloadGithubZipFile(gitHubCommit, tc.githubToken, tc.instance)
		if err == nil && err.errorCode != 500 {
			t.Fatalf("Expected error for bad repo values: %s/%s:%s", tc.repoOwner, tc.repoName, tc.gitTag)
		}
	}
}

func TestExtractFiles(t *testing.T) {
	t.Parallel()

	publicGitHub := GitHubInstance{
		BaseUrl: "github.com",
		ApiUrl:  "api.github.com",
	}

	cases := []struct {
		instance 		  GitHubInstance
		localFilePath     string
		filePathToExtract string
		expectedNumFiles  int
		nonemptyFiles     []string
	}{
		{publicGitHub, "test-fixtures/fetch-test-public-0.0.1.zip", "/", 1, nil},
		{publicGitHub, "test-fixtures/fetch-test-public-0.0.2.zip", "/", 2, nil},
		{publicGitHub, "test-fixtures/fetch-test-public-0.0.3.zip", "/", 4, []string{"/README.md"} },
		{publicGitHub, "test-fixtures/fetch-test-public-0.0.3.zip", "/folder", 2, nil},
	}

	for _, tc := range cases {
		// Create a temp directory
		tempDir, err := ioutil.TempDir("", "")
		if err != nil {
			t.Fatalf("Failed to create temp directory: %s", err)
		}
		defer os.RemoveAll(tempDir)

		err = extractFiles(tc.localFilePath, tc.filePathToExtract, tempDir)
		if err != nil {
			t.Fatalf("Failed to extract files: %s", err)
		}

		// Count the number of files in the directory
		var numFiles int
		filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
			if ! info.IsDir() {
				numFiles++
			}
			return nil
		})

		if (numFiles != tc.expectedNumFiles) {
			t.Fatalf("While extracting %s, expected to find %d file(s), but found %d. Local path = %s", tc.localFilePath, tc.expectedNumFiles, numFiles, tempDir)
		}

		// Ensure that files declared to be non-empty are in fact non-empty
		filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
			relativeFilename := strings.TrimPrefix(path, tempDir)

			if ! info.IsDir() && stringInSlice(relativeFilename, tc.nonemptyFiles) {
				if info.Size() == 0 {
					t.Fatalf("Expected %s in %s to have non-zero file size, but found file size = %d.\n", relativeFilename, tc.localFilePath, info.Size())
				}
			}
			return nil
		})

	}
}

// Return ture if the given slice contains the given string
func stringInSlice(s string, slice []string) bool {
	for _, val := range slice {
		if val == s {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
// this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, version 2.0.

package font

import (
	"bytes"
	"fmt"
	"github.com/richardwilkes/ui/geom"
	"sort"
	"unsafe"
)

const (
	// Bold when set, indicates the font will be rendered as bold.
	Bold Style = 1 << iota
	// ActualBold when set, indicates the font is actually bold.
	ActualBold
	// Italic when set, indicates the font will be rendered as italic.
	Italic
	// ActualItalic when set, indicates the font is actually italic.
	ActualItalic
	// Condensed when set, indicates the font will be rendered with less spacing than normal
	// between its characters. Cannot be set at the same time as Expanded.
	Condensed
	// Expanded when set, indicates the font will be rendered with more spacing than normal
	// between its characters. Cannot be set at the same time as Condensed.
	Expanded
	// Monospaced when set, indicates the font has a uniform width for each character.
	Monospaced
	// UserSettable contains the masks that can be set by a client.
	// All other bits will be ignored when the font is created.
	UserSettable = Bold | Italic | Condensed | Expanded
)

const (
	userID = iota
	userMonospacedID
	systemID
	emphasizedSystemID
	smallSystemID
	smallEmphasizedSystemID
	viewsID
	labelID
	menuID
	menuCmdKeyID
)

// Style holds the style flags for a Font.
type Style uint8

// Font holds font information.
type Font struct {
	font unsafe.Pointer
	desc Desc
}

// Desc holds information necessary to reconstruct a Font.
type Desc struct {
	Family string  // Family name of the Font, such as "Times New Roman".
	Size   float32 // Size of the font, in points.
	Style  Style   // Style flags.
}

type fontRegistry struct {
	font  *Font
	count int
}

var (
	descToRegMap  = make(map[Desc]*fontRegistry, 0)
	fontToDescMap = make(map[*Font]Desc, 0)
)

var (
	// UserDesc is the font used by default for documents and other text under the user’s control
	// (that is, text whose font the user can normally change).
	UserDesc = platformDesc(userID)
	// UserMonospacedDesc is the font used by default for documents and other text under the user’s
	// control when that font is fixed-pitch.
	UserMonospacedDesc = platformDesc(userMonospacedID)
	// SystemDesc is the system font used for standard user-interface items such as window titles,
	// button labels, etc.
	SystemDesc = platformDesc(systemID)
	// EmphasizedSystemDesc is the system font used for emphasis in alerts.
	EmphasizedSystemDesc = platformDesc(emphasizedSystemID)
	// SmallSystemDesc is the standard small system font used for informative text in alerts,
	// column headings in lists, help tags, utility window titles, toolbar item labels, tool
	// palettes, tool tips, and small controls.
	SmallSystemDesc = platformDesc(smallSystemID)
	// SmallEmphasizedSystemDesc is the small system font used for emphasis.
	SmallEmphasizedSystemDesc = platformDesc(smallEmphasizedSystemID)
	// ViewsDesc is the font used as the default font of text in lists and tables.
	ViewsDesc = platformDesc(viewsID)
	// LabelDesc is the font used for labels.
	LabelDesc = platformDesc(labelID)
	// MenuDesc is the font used for menus.
	MenuDesc = platformDesc(menuID)
	// MenuCmdKeyDesc is the font used for menu item command key equivalents.
	MenuCmdKeyDesc = platformDesc(menuCmdKeyID)
)

// Acquire a font and prepares it for use.
func Acquire(desc Desc) *Font {
	var reg *fontRegistry
	var ok bool
	if reg, ok = descToRegMap[desc]; !ok {
		reg = &fontRegistry{font: platformNewFont(desc)}
		descToRegMap[desc] = reg
		fontToDescMap[reg.font] = desc
	}
	reg.count++
	return reg.font
}

// PlatformPtr returns a pointer to the underlying platform-specific data.
func (font *Font) PlatformPtr() unsafe.Pointer {
	return font.font
}

// Release a font.
func (font *Font) Release() {
	if desc, ok := fontToDescMap[font]; ok {
		if reg, ok2 := descToRegMap[desc]; ok2 {
			reg.count--
			if reg.count < 1 {
				delete(descToRegMap, desc)
				delete(fontToDescMap, font)
				font.dispose()
			}
		} else {
			delete(fontToDescMap, font)
			font.dispose()
		}
	}
}

// Desc returns a copy of the font's description information.
func (font *Font) Desc() Desc {
	return font.desc
}

func (font *Font) dispose() {
	font.platformDispose()
	font.font = nil
}

// Ascent of the Font, i.e. the distance from the baseline to the top of a typical capital letter.
func (font *Font) Ascent() float32 {
	return font.platformAscent()
}

// Descent of the Font, i.e. the distance from the baseline to the bottom of the typical letter
// that has a descender, such as a lower case 'g'.
func (font *Font) Descent() float32 {
	return font.platformDescent()
}

// Leading of the Font, i.e. the recommended distance between the bottom of the descender line
// to the top of the next line.
func (font *Font) Leading() float32 {
	return font.platformLeading()
}

// Height of the font, i.e. Ascent() + Descent().
func (font *Font) Height() float32 {
	return font.Ascent() + font.Descent()
}

// Width of the string rendered with this font.
func (font *Font) Width(str string) float32 {
	return font.platformWidth(str)
}

// Size of the string rendered with this font.
func (font *Font) Size(str string) geom.Size {
	return geom.Size{Width: font.Width(str), Height: font.Height()}
}

func (font *Font) IndexForPosition(x float32, str string) int {
	return font.platformIndexForPosition(x, str)
}

func (font *Font) PositionForIndex(index int, str string) float32 {
	return font.platformPositionForIndex(index, str)
}

// String implements the fmt.Stringer interface.
func (font *Font) String() string {
	return font.desc.String()
}

// AvailableFontFamilies retrieves the names of the installed font families.
func AvailableFontFamilies() []string {
	families := platformAvailableFontFamilies()
	sort.Strings(families)
	return families
}

// Bold when set, indicates the font will be rendered as bold.
func (desc Desc) Bold() bool {
	return (desc.Style & Bold) == Bold
}

// ActualBold when set, indicates the font is actually bold.
func (desc Desc) ActualBold() bool {
	return (desc.Style & ActualBold) == ActualBold
}

// Italic when set, indicates the font will be rendered as italic.
func (desc Desc) Italic() bool {
	return (desc.Style & Italic) == Italic
}

// ActualItalic when set, indicates the font is actually italic.
func (desc Desc) ActualItalic() bool {
	return (desc.Style & ActualItalic) == ActualItalic
}

// Condensed when set, indicates the font will be rendered with less spacing than normal between
// its characters.
func (desc Desc) Condensed() bool {
	return (desc.Style & Condensed) == Condensed
}

// Expanded when set, indicates the font will be rendered with more spacing than normal between
// its characters.
func (desc Desc) Expanded() bool {
	return (desc.Style & Expanded) == Expanded
}

// Monospaced when set, indicates the font has a uniform width for each character.
func (desc Desc) Monospaced() bool {
	return (desc.Style & Monospaced) == Monospaced
}

// String implements the fmt.Stringer interface.
func (desc Desc) String() string {
	var buffer bytes.Buffer
	buffer.WriteString(desc.Family)
	fmt.Fprintf(&buffer, " %v", desc.Size)
	if desc.Bold() {
		buffer.WriteString(" bold")
		if !desc.ActualBold() {
			buffer.WriteString(" (synthetic)")
		}
	}
	if desc.Italic() {
		buffer.WriteString(" italic")
		if !desc.ActualItalic() {
			buffer.WriteString(" (synthetic)")
		}
	}
	if desc.Condensed() {
		buffer.WriteString(" condensed")
	}
	if desc.Expanded() {
		buffer.WriteString(" expanded")
	}
	if desc.Monospaced() {
		buffer.WriteString(" monospaced")
	}
	return buffer.String()
}
<commit_msg>Add missing comments<commit_after>// Copyright (c) 2016 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
// this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, version 2.0.

package font

import (
	"bytes"
	"fmt"
	"github.com/richardwilkes/ui/geom"
	"sort"
	"unsafe"
)

const (
	// Bold when set, indicates the font will be rendered as bold.
	Bold Style = 1 << iota
	// ActualBold when set, indicates the font is actually bold.
	ActualBold
	// Italic when set, indicates the font will be rendered as italic.
	Italic
	// ActualItalic when set, indicates the font is actually italic.
	ActualItalic
	// Condensed when set, indicates the font will be rendered with less spacing than normal
	// between its characters. Cannot be set at the same time as Expanded.
	Condensed
	// Expanded when set, indicates the font will be rendered with more spacing than normal
	// between its characters. Cannot be set at the same time as Condensed.
	Expanded
	// Monospaced when set, indicates the font has a uniform width for each character.
	Monospaced
	// UserSettable contains the masks that can be set by a client.
	// All other bits will be ignored when the font is created.
	UserSettable = Bold | Italic | Condensed | Expanded
)

const (
	userID = iota
	userMonospacedID
	systemID
	emphasizedSystemID
	smallSystemID
	smallEmphasizedSystemID
	viewsID
	labelID
	menuID
	menuCmdKeyID
)

// Style holds the style flags for a Font.
type Style uint8

// Font holds font information.
type Font struct {
	font unsafe.Pointer
	desc Desc
}

// Desc holds information necessary to reconstruct a Font.
type Desc struct {
	Family string  // Family name of the Font, such as "Times New Roman".
	Size   float32 // Size of the font, in points.
	Style  Style   // Style flags.
}

type fontRegistry struct {
	font  *Font
	count int
}

var (
	descToRegMap  = make(map[Desc]*fontRegistry, 0)
	fontToDescMap = make(map[*Font]Desc, 0)
)

var (
	// UserDesc is the font used by default for documents and other text under the user’s control
	// (that is, text whose font the user can normally change).
	UserDesc = platformDesc(userID)
	// UserMonospacedDesc is the font used by default for documents and other text under the user’s
	// control when that font is fixed-pitch.
	UserMonospacedDesc = platformDesc(userMonospacedID)
	// SystemDesc is the system font used for standard user-interface items such as window titles,
	// button labels, etc.
	SystemDesc = platformDesc(systemID)
	// EmphasizedSystemDesc is the system font used for emphasis in alerts.
	EmphasizedSystemDesc = platformDesc(emphasizedSystemID)
	// SmallSystemDesc is the standard small system font used for informative text in alerts,
	// column headings in lists, help tags, utility window titles, toolbar item labels, tool
	// palettes, tool tips, and small controls.
	SmallSystemDesc = platformDesc(smallSystemID)
	// SmallEmphasizedSystemDesc is the small system font used for emphasis.
	SmallEmphasizedSystemDesc = platformDesc(smallEmphasizedSystemID)
	// ViewsDesc is the font used as the default font of text in lists and tables.
	ViewsDesc = platformDesc(viewsID)
	// LabelDesc is the font used for labels.
	LabelDesc = platformDesc(labelID)
	// MenuDesc is the font used for menus.
	MenuDesc = platformDesc(menuID)
	// MenuCmdKeyDesc is the font used for menu item command key equivalents.
	MenuCmdKeyDesc = platformDesc(menuCmdKeyID)
)

// Acquire a font and prepares it for use.
func Acquire(desc Desc) *Font {
	var reg *fontRegistry
	var ok bool
	if reg, ok = descToRegMap[desc]; !ok {
		reg = &fontRegistry{font: platformNewFont(desc)}
		descToRegMap[desc] = reg
		fontToDescMap[reg.font] = desc
	}
	reg.count++
	return reg.font
}

// PlatformPtr returns a pointer to the underlying platform-specific data.
func (font *Font) PlatformPtr() unsafe.Pointer {
	return font.font
}

// Release a font.
func (font *Font) Release() {
	if desc, ok := fontToDescMap[font]; ok {
		if reg, ok2 := descToRegMap[desc]; ok2 {
			reg.count--
			if reg.count < 1 {
				delete(descToRegMap, desc)
				delete(fontToDescMap, font)
				font.dispose()
			}
		} else {
			delete(fontToDescMap, font)
			font.dispose()
		}
	}
}

// Desc returns a copy of the font's description information.
func (font *Font) Desc() Desc {
	return font.desc
}

func (font *Font) dispose() {
	font.platformDispose()
	font.font = nil
}

// Ascent of the Font, i.e. the distance from the baseline to the top of a typical capital letter.
func (font *Font) Ascent() float32 {
	return font.platformAscent()
}

// Descent of the Font, i.e. the distance from the baseline to the bottom of the typical letter
// that has a descender, such as a lower case 'g'.
func (font *Font) Descent() float32 {
	return font.platformDescent()
}

// Leading of the Font, i.e. the recommended distance between the bottom of the descender line
// to the top of the next line.
func (font *Font) Leading() float32 {
	return font.platformLeading()
}

// Height of the font, i.e. Ascent() + Descent().
func (font *Font) Height() float32 {
	return font.Ascent() + font.Descent()
}

// Width of the string rendered with this font.
func (font *Font) Width(str string) float32 {
	return font.platformWidth(str)
}

// Size of the string rendered with this font.
func (font *Font) Size(str string) geom.Size {
	return geom.Size{Width: font.Width(str), Height: font.Height()}
}

// IndexForPosition returns the rune index within the string for the specified x-coordinate, where
// 0 is the start of the string.
func (font *Font) IndexForPosition(x float32, str string) int {
	return font.platformIndexForPosition(x, str)
}

// PositionForIndex returns the x-coordinate where the specified rune index starts. The returned
// coordinate assumes 0 is the start of the string.
func (font *Font) PositionForIndex(index int, str string) float32 {
	return font.platformPositionForIndex(index, str)
}

// String implements the fmt.Stringer interface.
func (font *Font) String() string {
	return font.desc.String()
}

// AvailableFontFamilies retrieves the names of the installed font families.
func AvailableFontFamilies() []string {
	families := platformAvailableFontFamilies()
	sort.Strings(families)
	return families
}

// Bold when set, indicates the font will be rendered as bold.
func (desc Desc) Bold() bool {
	return (desc.Style & Bold) == Bold
}

// ActualBold when set, indicates the font is actually bold.
func (desc Desc) ActualBold() bool {
	return (desc.Style & ActualBold) == ActualBold
}

// Italic when set, indicates the font will be rendered as italic.
func (desc Desc) Italic() bool {
	return (desc.Style & Italic) == Italic
}

// ActualItalic when set, indicates the font is actually italic.
func (desc Desc) ActualItalic() bool {
	return (desc.Style & ActualItalic) == ActualItalic
}

// Condensed when set, indicates the font will be rendered with less spacing than normal between
// its characters.
func (desc Desc) Condensed() bool {
	return (desc.Style & Condensed) == Condensed
}

// Expanded when set, indicates the font will be rendered with more spacing than normal between
// its characters.
func (desc Desc) Expanded() bool {
	return (desc.Style & Expanded) == Expanded
}

// Monospaced when set, indicates the font has a uniform width for each character.
func (desc Desc) Monospaced() bool {
	return (desc.Style & Monospaced) == Monospaced
}

// String implements the fmt.Stringer interface.
func (desc Desc) String() string {
	var buffer bytes.Buffer
	buffer.WriteString(desc.Family)
	fmt.Fprintf(&buffer, " %v", desc.Size)
	if desc.Bold() {
		buffer.WriteString(" bold")
		if !desc.ActualBold() {
			buffer.WriteString(" (synthetic)")
		}
	}
	if desc.Italic() {
		buffer.WriteString(" italic")
		if !desc.ActualItalic() {
			buffer.WriteString(" (synthetic)")
		}
	}
	if desc.Condensed() {
		buffer.WriteString(" condensed")
	}
	if desc.Expanded() {
		buffer.WriteString(" expanded")
	}
	if desc.Monospaced() {
		buffer.WriteString(" monospaced")
	}
	return buffer.String()
}
<|endoftext|>


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

package wag

import (
	"bytes"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"testing"

	"github.com/tsavola/wag/internal/test/fuzzutil"
)

func TestFuzzCorpus(t *testing.T)   { testFuzzDir(t, "testdata/fuzz/corpus") }
func TestFuzzCrashers(t *testing.T) { testFuzzDir(t, "testdata/fuzz/crashers") }

func testFuzzDir(t *testing.T, dir string) {
	infos, err := ioutil.ReadDir(dir)
	if err != nil {
		if os.IsNotExist(err) {
			return
		}
		t.Fatal(err)
	}

	var tested bool

	for _, info := range infos {
		if !strings.Contains(info.Name(), ".") {
			t.Run(info.Name(), func(t *testing.T) {
				t.Parallel()
				testFuzzFile(t, path.Join(dir, info.Name()))
			})

			tested = true
		}
	}

	if !tested {
		t.Skipf("%s does not contain any samples", dir)
	}
}

func testFuzzFile(t *testing.T, filename string) {
	t.Helper()

	data, err := ioutil.ReadFile(filename)
	if err != nil {
		t.Fatal(err)
	}

	config := &Config{
		ImportResolver: fuzzutil.Resolver,
		Text:           fuzzutil.NewTextBuffer(),
		GlobalsMemory:  fuzzutil.NewGlobalsMemoryBuffer(),
	}

	_, err = Compile(config, bytes.NewReader(data), lib)
	if _, ok := fuzzutil.Result(err); !ok {
		t.Error(err)
	}
}
<commit_msg>fuzz fix<commit_after>// Copyright (c) 2016 Timo Savola. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package wag

import (
	"bytes"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"testing"

	"github.com/tsavola/wag/internal/test/fuzzutil"
)

func TestFuzzCorpus(t *testing.T)   { testFuzzDir(t, "testdata/fuzz/corpus") }
func TestFuzzCrashers(t *testing.T) { testFuzzDir(t, "testdata/fuzz/crashers") }

func testFuzzDir(t *testing.T, dir string) {
	infos, err := ioutil.ReadDir(dir)
	if err != nil {
		if os.IsNotExist(err) {
			return
		}
		t.Fatal(err)
	}

	var tested bool

	for _, info := range infos {
		if !strings.Contains(info.Name(), ".") {
			filename := path.Join(dir, info.Name())

			t.Run(info.Name(), func(t *testing.T) {
				t.Parallel()
				testFuzzFile(t, filename)
			})

			tested = true
		}
	}

	if !tested {
		t.Skipf("%s does not contain any samples", dir)
	}
}

func testFuzzFile(t *testing.T, filename string) {
	t.Helper()

	data, err := ioutil.ReadFile(filename)
	if err != nil {
		t.Fatal(err)
	}

	config := &Config{
		ImportResolver: fuzzutil.Resolver,
		Text:           fuzzutil.NewTextBuffer(),
		GlobalsMemory:  fuzzutil.NewGlobalsMemoryBuffer(),
	}

	_, err = Compile(config, bytes.NewReader(data), lib)
	if _, ok := fuzzutil.Result(err); !ok {
		t.Error(err)
	}
}
<|endoftext|>