<commit_before>package main

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/hwaf/hwaf/hlib"
)

// map of pkgname -> libname
//  if empty => ignore dep.
var g_pkg_map = map[string]string{
	"AtlasCLHEP":         "CLHEP",
	"AtlasCOOL":          "COOL",
	"AtlasCORAL":         "CORAL",
	"AtlasCxxPolicy":     "",
	"AtlasFortranPolicy": "",
	"AtlasPOOL":          "POOL",
	"AtlasPython":        "python",
	"AtlasROOT":          "ROOT",
	"AtlasReflex":        "Reflex",
	"AtlasPolicy":        "",
	"ExternalPolicy":     "",
	"GaudiInterface":     "GaudiKernel",
}

func find_tgt(wscript *hlib.Wscript_t, name string) (int, *hlib.Target_t) {
	wbld := &wscript.Build
	for i := range wbld.Targets {
		if wbld.Targets[i].Name == name {
			return i, &wbld.Targets[i]
		}
	}
	return -1, nil
}

func use_list(wscript *hlib.Wscript_t) []string {
	uses := []string{}
	for _, dep := range wscript.Package.Deps {
		pkg := filepath.Base(dep.Name)
		use_pkg, ok := g_pkg_map[pkg]
		if !ok {
			use_pkg = pkg
		}
		if use_pkg != "" {
			uses = append(uses, use_pkg)
		}
	}
	return uses
}

func cmt_arg_map(args []string) map[string]string {
	o := make(map[string]string, len(args))
	for _, v := range args {
		idx := strings.Index(v, "=")
		if idx < 0 {
			panic(fmt.Errorf("cmt2yml: could not find '=' in string [%s]", v))
		}
		if idx < 1 {
			panic(fmt.Errorf("cmt2yml: malformed string [%s]", v))
		}
		kk := v[:idx]
		vv := v[idx+1:]
		if vv == "" {
			panic(fmt.Errorf("cmt2yml: malformed string [%s]", v))
		}
		if vv[0] == '"' {
			vv = vv[1:]
		}
		if strings.HasPrefix(vv, "../") {
			vv = vv[len("../"):]
		}
		o[kk] = vv
	}
	return o
}

func cnv_atlas_library(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	libname := ""
	switch len(x.Args) {
	case 0:
		// installed_library pattern
		libname = filepath.Base(wscript.Package.Name)
	default:
		// named_installed_library pattern
		margs := cmt_arg_map(x.Args)
		libname = margs["library"]
	}
	if libname == "" {
		return fmt.Errorf(
			"cmt2yml: empty atlas_library name (package=%s, args=%v)",
			wscript.Package.Name,
			x.Args,
		)
	}
	itgt, tgt := find_tgt(wscript, libname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: libname},
		)
		itgt, tgt = find_tgt(wscript, libname)
	}
	tgt.Features = []string{"atlas_library"}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}

	//fmt.Printf(">>> [%v] \n", *tgt)
	return nil
}

func cnv_atlas_component_library(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	libname := ""
	switch len(x.Args) {
	case 0:
		// component_library pattern
		libname = filepath.Base(wscript.Package.Name)
	default:
		// named_component_library pattern
		margs := cmt_arg_map(x.Args)
		libname = margs["library"]
	}
	if libname == "" {
		return fmt.Errorf(
			"cmt2yml: empty atlas_component name (package=%s, args=%v)",
			wscript.Package.Name,
			x.Args,
		)
	}
	itgt, tgt := find_tgt(wscript, libname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: libname},
		)
		itgt, tgt = find_tgt(wscript, libname)
	}
	tgt.Features = []string{"atlas_component"}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}

	//fmt.Printf(">>> component [%v]...\n", *tgt)
	return nil
}

func cnv_atlas_dual_use_library(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	libname := ""
	switch len(x.Args) {
	case 0:
		// dual_use_library pattern
		libname = filepath.Base(wscript.Package.Name)
	default:
		// named_dual_use_library pattern
		margs := cmt_arg_map(x.Args)
		if _, ok := margs["library"]; ok {
			libname = margs["library"]
		} else {
			libname = filepath.Base(wscript.Package.Name)
		}

	}
	if libname == "" {
		return fmt.Errorf(
			"cmt2yml: empty atlas_dual_use_library name (package=%s, args=%v)",
			wscript.Package.Name,
			x.Args,
		)
	}
	itgt, tgt := find_tgt(wscript, libname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: libname},
		)
		itgt, tgt = find_tgt(wscript, libname)
	}
	tgt.Features = []string{"atlas_dual_use_library"}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}

	fmt.Printf(">>> [%v] \n", *tgt)
	return nil
}

func cnv_atlas_tpcnv_library(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	libname := ""
	switch len(x.Args) {
	case 0:
		// tpcnv_library pattern
		libname = filepath.Base(wscript.Package.Name)
	default:
		// named_tpcnv_library pattern
		margs := cmt_arg_map(x.Args)
		libname = margs["name"]
	}
	if libname == "" {
		return fmt.Errorf(
			"cmt2yml: empty atlas_tpcnv name (package=%s, args=%v)",
			wscript.Package.Name,
			x.Args,
		)
	}
	itgt, tgt := find_tgt(wscript, libname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: libname},
		)
		itgt, tgt = find_tgt(wscript, libname)
	}
	tgt.Features = []string{"atlas_tpcnv"}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}

	fmt.Printf(">>> [%v] \n", *tgt)
	return nil
}

func cnv_atlas_install_joboptions(wscript *hlib.Wscript_t, stmt Stmt) error {
	//x := stmt.(*ApplyPattern)
	//fmt.Printf(">>> [%s] \n", x.Name)
	pkgname := filepath.Base(wscript.Package.Name)
	tgt := hlib.Target_t{Name: pkgname + "-install-jobos"}
	tgt.Features = []string{"atlas_install_joboptions"}
	tgt.Source = []hlib.Value{hlib.DefaultValue(
		"jobos",
		[]string{"share/*.py", "share/*.txt"},
	)}
	wscript.Build.Targets = append(wscript.Build.Targets, tgt)
	return nil
}

func cnv_atlas_install_python_modules(wscript *hlib.Wscript_t, stmt Stmt) error {
	//x := stmt.(*ApplyPattern)
	//fmt.Printf(">>> [%s] \n", x.Name)
	pkgname := filepath.Base(wscript.Package.Name)
	tgt := hlib.Target_t{Name: pkgname + "-install-py"}
	tgt.Features = []string{"atlas_install_python_modules"}
	tgt.Source = []hlib.Value{hlib.DefaultValue(
		"python-files",
		[]string{"python/*.py"},
	)}
	wscript.Build.Targets = append(wscript.Build.Targets, tgt)
	return nil
}

func cnv_atlas_install_scripts(wscript *hlib.Wscript_t, stmt Stmt) error {
	//x := stmt.(*ApplyPattern)
	//fmt.Printf(">>> [%s] \n", x.Name)
	pkgname := filepath.Base(wscript.Package.Name)
	tgt := hlib.Target_t{Name: pkgname + "-install-scripts"}
	tgt.Features = []string{"atlas_install_scripts"}
	tgt.Source = []hlib.Value{hlib.DefaultValue(
		"script-files",
		[]string{"scripts/*"},
	)}
	wscript.Build.Targets = append(wscript.Build.Targets, tgt)
	return nil
}

func cnv_atlas_install_xmls(wscript *hlib.Wscript_t, stmt Stmt) error {
	//x := stmt.(*ApplyPattern)
	//fmt.Printf(">>> [%s] \n", x.Name)
	pkgname := filepath.Base(wscript.Package.Name)
	tgt := hlib.Target_t{Name: pkgname + "-install-xmls"}
	tgt.Features = []string{"atlas_install_xmls"}
	tgt.Source = []hlib.Value{hlib.DefaultValue(
		"xml-files",
		[]string{"xml/*"},
	)}
	wscript.Build.Targets = append(wscript.Build.Targets, tgt)
	return nil
}

func cnv_atlas_install_data(wscript *hlib.Wscript_t, stmt Stmt) error {
	//x := stmt.(*ApplyPattern)
	//fmt.Printf(">>> [%s] \n", x.Name)
	pkgname := filepath.Base(wscript.Package.Name)
	tgt := hlib.Target_t{Name: pkgname + "-install-data"}
	tgt.Features = []string{"atlas_install_data"}
	tgt.Source = []hlib.Value{hlib.DefaultValue(
		"data-files",
		[]string{"data/*"},
	)}
	wscript.Build.Targets = append(wscript.Build.Targets, tgt)
	return nil
}

func cnv_atlas_install_java(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	fmt.Printf(">>> [%s] \n", x.Name)
	return nil
}

func cnv_atlas_dictionary(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	margs := cmt_arg_map(x.Args)
	pkgname := filepath.Base(wscript.Package.Name)
	libname := margs["dict"] + "Dict"
	selfile := pkgname + "/" + margs["selectionfile"]
	hdrfile := margs["headerfiles"]

	itgt, tgt := find_tgt(wscript, libname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: libname},
		)
		itgt, tgt = find_tgt(wscript, libname)
	}
	tgt.Features = []string{"atlas_dictionary"}
	tgt.Source = []hlib.Value{hlib.DefaultValue("source", []string{hdrfile})}
	if tgt.KwArgs == nil {
		tgt.KwArgs = make(map[string][]hlib.Value)
	}
	tgt.KwArgs["selection_file"] = []hlib.Value{hlib.DefaultValue("selfile", []string{selfile})}
	//tgt.Use = []hlib.Value{hlib.DefaultValue("uses", use_list(wscript))}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}
	fmt.Printf(">>> %v\n", *tgt)
	return nil
}

func cnv_atlas_unittest(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	margs := cmt_arg_map(x.Args)
	pkgname := filepath.Base(wscript.Package.Name)
	name := margs["unit_test"]
	tgtname := fmt.Sprintf("%s-test-%s", pkgname, name)
	extra := margs["extrapatterns"]
	source := fmt.Sprintf("test/%s_test.cxx", name)

	itgt, tgt := find_tgt(wscript, tgtname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: tgtname},
		)
		itgt, tgt = find_tgt(wscript, tgtname)
	}
	tgt.Features = []string{"atlas_unittest"}
	tgt.Source = []hlib.Value{hlib.DefaultValue("source", []string{source})}
	if tgt.KwArgs == nil {
		tgt.KwArgs = make(map[string][]hlib.Value)
	}
	if extra != "" {
		tgt.KwArgs["extrapatterns"] = []hlib.Value{
			hlib.DefaultValue("extrapatterns", []string{extra}),
		}
	}
	//tgt.Use = []hlib.Value{hlib.DefaultValue("uses", use_list(wscript))}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}
	fmt.Printf(">>> %v\n", *tgt)
	return nil
}

// EOF
<commit_msg>cnv: less verbose<commit_after>package main

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/hwaf/hwaf/hlib"
)

// map of pkgname -> libname
//  if empty => ignore dep.
var g_pkg_map = map[string]string{
	"AtlasCLHEP":         "CLHEP",
	"AtlasCOOL":          "COOL",
	"AtlasCORAL":         "CORAL",
	"AtlasCxxPolicy":     "",
	"AtlasFortranPolicy": "",
	"AtlasPOOL":          "POOL",
	"AtlasPython":        "python",
	"AtlasROOT":          "ROOT",
	"AtlasReflex":        "Reflex",
	"AtlasPolicy":        "",
	"ExternalPolicy":     "",
	"GaudiInterface":     "GaudiKernel",
}

func find_tgt(wscript *hlib.Wscript_t, name string) (int, *hlib.Target_t) {
	wbld := &wscript.Build
	for i := range wbld.Targets {
		if wbld.Targets[i].Name == name {
			return i, &wbld.Targets[i]
		}
	}
	return -1, nil
}

func use_list(wscript *hlib.Wscript_t) []string {
	uses := []string{}
	for _, dep := range wscript.Package.Deps {
		pkg := filepath.Base(dep.Name)
		use_pkg, ok := g_pkg_map[pkg]
		if !ok {
			use_pkg = pkg
		}
		if use_pkg != "" {
			uses = append(uses, use_pkg)
		}
	}
	return uses
}

func cmt_arg_map(args []string) map[string]string {
	o := make(map[string]string, len(args))
	for _, v := range args {
		idx := strings.Index(v, "=")
		if idx < 0 {
			panic(fmt.Errorf("cmt2yml: could not find '=' in string [%s]", v))
		}
		if idx < 1 {
			panic(fmt.Errorf("cmt2yml: malformed string [%s]", v))
		}
		kk := v[:idx]
		vv := v[idx+1:]
		if vv == "" {
			panic(fmt.Errorf("cmt2yml: malformed string [%s]", v))
		}
		if vv[0] == '"' {
			vv = vv[1:]
		}
		if strings.HasPrefix(vv, "../") {
			vv = vv[len("../"):]
		}
		o[kk] = vv
	}
	return o
}

func cnv_atlas_library(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	libname := ""
	switch len(x.Args) {
	case 0:
		// installed_library pattern
		libname = filepath.Base(wscript.Package.Name)
	default:
		// named_installed_library pattern
		margs := cmt_arg_map(x.Args)
		libname = margs["library"]
	}
	if libname == "" {
		return fmt.Errorf(
			"cmt2yml: empty atlas_library name (package=%s, args=%v)",
			wscript.Package.Name,
			x.Args,
		)
	}
	itgt, tgt := find_tgt(wscript, libname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: libname},
		)
		itgt, tgt = find_tgt(wscript, libname)
	}
	tgt.Features = []string{"atlas_library"}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}

	//fmt.Printf(">>> [%v] \n", *tgt)
	return nil
}

func cnv_atlas_component_library(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	libname := ""
	switch len(x.Args) {
	case 0:
		// component_library pattern
		libname = filepath.Base(wscript.Package.Name)
	default:
		// named_component_library pattern
		margs := cmt_arg_map(x.Args)
		libname = margs["library"]
	}
	if libname == "" {
		return fmt.Errorf(
			"cmt2yml: empty atlas_component name (package=%s, args=%v)",
			wscript.Package.Name,
			x.Args,
		)
	}
	itgt, tgt := find_tgt(wscript, libname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: libname},
		)
		itgt, tgt = find_tgt(wscript, libname)
	}
	tgt.Features = []string{"atlas_component"}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}

	//fmt.Printf(">>> component [%v]...\n", *tgt)
	return nil
}

func cnv_atlas_dual_use_library(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	libname := ""
	switch len(x.Args) {
	case 0:
		// dual_use_library pattern
		libname = filepath.Base(wscript.Package.Name)
	default:
		// named_dual_use_library pattern
		margs := cmt_arg_map(x.Args)
		if _, ok := margs["library"]; ok {
			libname = margs["library"]
		} else {
			libname = filepath.Base(wscript.Package.Name)
		}

	}
	if libname == "" {
		return fmt.Errorf(
			"cmt2yml: empty atlas_dual_use_library name (package=%s, args=%v)",
			wscript.Package.Name,
			x.Args,
		)
	}
	itgt, tgt := find_tgt(wscript, libname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: libname},
		)
		itgt, tgt = find_tgt(wscript, libname)
	}
	tgt.Features = []string{"atlas_dual_use_library"}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}

	//fmt.Printf(">>> [%v] \n", *tgt)
	return nil
}

func cnv_atlas_tpcnv_library(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	libname := ""
	switch len(x.Args) {
	case 0:
		// tpcnv_library pattern
		libname = filepath.Base(wscript.Package.Name)
	default:
		// named_tpcnv_library pattern
		margs := cmt_arg_map(x.Args)
		libname = margs["name"]
	}
	if libname == "" {
		return fmt.Errorf(
			"cmt2yml: empty atlas_tpcnv name (package=%s, args=%v)",
			wscript.Package.Name,
			x.Args,
		)
	}
	itgt, tgt := find_tgt(wscript, libname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: libname},
		)
		itgt, tgt = find_tgt(wscript, libname)
	}
	tgt.Features = []string{"atlas_tpcnv"}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}

	//fmt.Printf(">>> [%v] \n", *tgt)
	return nil
}

func cnv_atlas_install_joboptions(wscript *hlib.Wscript_t, stmt Stmt) error {
	//x := stmt.(*ApplyPattern)
	//fmt.Printf(">>> [%s] \n", x.Name)
	pkgname := filepath.Base(wscript.Package.Name)
	tgt := hlib.Target_t{Name: pkgname + "-install-jobos"}
	tgt.Features = []string{"atlas_install_joboptions"}
	tgt.Source = []hlib.Value{hlib.DefaultValue(
		"jobos",
		[]string{"share/*.py", "share/*.txt"},
	)}
	wscript.Build.Targets = append(wscript.Build.Targets, tgt)
	return nil
}

func cnv_atlas_install_python_modules(wscript *hlib.Wscript_t, stmt Stmt) error {
	//x := stmt.(*ApplyPattern)
	//fmt.Printf(">>> [%s] \n", x.Name)
	pkgname := filepath.Base(wscript.Package.Name)
	tgt := hlib.Target_t{Name: pkgname + "-install-py"}
	tgt.Features = []string{"atlas_install_python_modules"}
	tgt.Source = []hlib.Value{hlib.DefaultValue(
		"python-files",
		[]string{"python/*.py"},
	)}
	wscript.Build.Targets = append(wscript.Build.Targets, tgt)
	return nil
}

func cnv_atlas_install_scripts(wscript *hlib.Wscript_t, stmt Stmt) error {
	//x := stmt.(*ApplyPattern)
	//fmt.Printf(">>> [%s] \n", x.Name)
	pkgname := filepath.Base(wscript.Package.Name)
	tgt := hlib.Target_t{Name: pkgname + "-install-scripts"}
	tgt.Features = []string{"atlas_install_scripts"}
	tgt.Source = []hlib.Value{hlib.DefaultValue(
		"script-files",
		[]string{"scripts/*"},
	)}
	wscript.Build.Targets = append(wscript.Build.Targets, tgt)
	return nil
}

func cnv_atlas_install_xmls(wscript *hlib.Wscript_t, stmt Stmt) error {
	//x := stmt.(*ApplyPattern)
	//fmt.Printf(">>> [%s] \n", x.Name)
	pkgname := filepath.Base(wscript.Package.Name)
	tgt := hlib.Target_t{Name: pkgname + "-install-xmls"}
	tgt.Features = []string{"atlas_install_xmls"}
	tgt.Source = []hlib.Value{hlib.DefaultValue(
		"xml-files",
		[]string{"xml/*"},
	)}
	wscript.Build.Targets = append(wscript.Build.Targets, tgt)
	return nil
}

func cnv_atlas_install_data(wscript *hlib.Wscript_t, stmt Stmt) error {
	//x := stmt.(*ApplyPattern)
	//fmt.Printf(">>> [%s] \n", x.Name)
	pkgname := filepath.Base(wscript.Package.Name)
	tgt := hlib.Target_t{Name: pkgname + "-install-data"}
	tgt.Features = []string{"atlas_install_data"}
	tgt.Source = []hlib.Value{hlib.DefaultValue(
		"data-files",
		[]string{"data/*"},
	)}
	wscript.Build.Targets = append(wscript.Build.Targets, tgt)
	return nil
}

func cnv_atlas_install_java(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	fmt.Printf(">>> [%s] \n", x.Name)
	return nil
}

func cnv_atlas_dictionary(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	margs := cmt_arg_map(x.Args)
	pkgname := filepath.Base(wscript.Package.Name)
	libname := margs["dict"] + "Dict"
	selfile := pkgname + "/" + margs["selectionfile"]
	hdrfile := margs["headerfiles"]

	itgt, tgt := find_tgt(wscript, libname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: libname},
		)
		itgt, tgt = find_tgt(wscript, libname)
	}
	tgt.Features = []string{"atlas_dictionary"}
	tgt.Source = []hlib.Value{hlib.DefaultValue("source", []string{hdrfile})}
	if tgt.KwArgs == nil {
		tgt.KwArgs = make(map[string][]hlib.Value)
	}
	tgt.KwArgs["selection_file"] = []hlib.Value{hlib.DefaultValue("selfile", []string{selfile})}
	//tgt.Use = []hlib.Value{hlib.DefaultValue("uses", use_list(wscript))}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}
	//fmt.Printf(">>> %v\n", *tgt)
	return nil
}

func cnv_atlas_unittest(wscript *hlib.Wscript_t, stmt Stmt) error {
	x := stmt.(*ApplyPattern)
	margs := cmt_arg_map(x.Args)
	pkgname := filepath.Base(wscript.Package.Name)
	name := margs["unit_test"]
	tgtname := fmt.Sprintf("%s-test-%s", pkgname, name)
	extra := margs["extrapatterns"]
	source := fmt.Sprintf("test/%s_test.cxx", name)

	itgt, tgt := find_tgt(wscript, tgtname)
	if itgt < 0 {
		wscript.Build.Targets = append(
			wscript.Build.Targets,
			hlib.Target_t{Name: tgtname},
		)
		itgt, tgt = find_tgt(wscript, tgtname)
	}
	tgt.Features = []string{"atlas_unittest"}
	tgt.Source = []hlib.Value{hlib.DefaultValue("source", []string{source})}
	if tgt.KwArgs == nil {
		tgt.KwArgs = make(map[string][]hlib.Value)
	}
	if extra != "" {
		tgt.KwArgs["extrapatterns"] = []hlib.Value{
			hlib.DefaultValue("extrapatterns", []string{extra}),
		}
	}
	//tgt.Use = []hlib.Value{hlib.DefaultValue("uses", use_list(wscript))}
	uses := use_list(wscript)
	if len(uses) > 0 {
		tgt.Use = []hlib.Value{hlib.DefaultValue("uses", uses)}
	}
	//fmt.Printf(">>> %v\n", *tgt)
	return nil
}

// EOF
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package azure

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/instance"
	"launchpad.net/loggo"
)

// Register the Azure provider with Juju.
func init() {
	environs.RegisterProvider("azure", azureEnvironProvider{})
}

// Logger for the Azure provider.
var logger = loggo.GetLogger("juju.environs.azure")

type azureEnvironProvider struct{}

// azureEnvironProvider implements EnvironProvider.
var _ environs.EnvironProvider = (*azureEnvironProvider)(nil)

// Open is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
	logger.Debugf("opening environment %q.", cfg.Name())
	environ, err := NewEnviron(cfg)
	if err != nil {
		return nil, err
	}
	return environ, nil
}

// PublicAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PublicAddress() (string, error) {
	config, err := parseWALAConfig()
	if err != nil {
		logger.Errorf("error parsing Windows Azure Linux Agent config file (%q): %v", _WALAConfigPath, err)
		return "", err
	}
	return config.getDeploymentFQDN(), nil
}

// PrivateAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PrivateAddress() (string, error) {
	// This returns the instance's *public* address for now.
	// We need to figure out how to do instance-to-instance
	// communication using the private IP before we can use the Azure
	// private address.
	return prov.PublicAddress()
	/*
		config, err := parseWALAConfig()
		if err != nil {
			logger.Errorf("error parsing Windows Azure Linux Agent config file (%q): %v", _WALAConfigPath, err)
			return "", err
		}
		return config.getInternalIP(), nil
	*/
}

// InstanceId is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) InstanceId() (instance.Id, error) {
	config, err := parseWALAConfig()
	if err != nil {
		logger.Errorf("error parsing WALA config file (%q): %v", _WALAConfigPath, err)
		return instance.Id(""), err
	}
	return instance.Id(config.getDeploymentName()), nil
}

// The XML Windows Azure Linux Agent (WALA) is the agent which runs on all
// the Linux Azure VMs.  The hostname of the VM is the service name and the
// juju instanceId is (by design), the deployment's name.
//
// See https://github.com/windows-azure/walinuxagent for more details.
//
// Here is an example content of such a config file:
// <?xml version="1.0" encoding="utf-8"?>
// <SharedConfig version="1.0.0.0" goalStateIncarnation="1">
//   <Deployment name="b6de4c4c7d4a49c39270e0c57481fd9b" guid="{495985a8-8e5a-49aa-826f-d1f7f51045b6}" incarnation="0">
//    <Service name="gwaclmachineex95rsek" guid="{00000000-0000-0000-0000-000000000000}" />
//    <ServiceInstance name="b6de4c4c7d4a49c39270e0c57481fd9b.0" guid="{9806cac7-e566-42b8-9ecb-de8da8f69893}" />
//  [...]
//  <Instances>
//    <Instance id="gwaclroleldc1o5p" address="10.76.200.59">
//      [...]
//    </Instance>
//  </Instances>
//  </Deployment>
// </SharedConfig>

// Structures used to parse the XML Windows Azure Linux Agent (WALA)
// configuration file.

type WALASharedConfig struct {
	XMLName    xml.Name       `xml:"SharedConfig"`
	Deployment WALADeployment `xml:"Deployment"`
	Instances  []WALAInstance `xml:"Instances>Instance"`
}

// getDeploymentName returns the deployment name referenced by the
// configuration.
// Confusingly, this is stored in the 'name' attribute of the 'Service'
// element.
func (config *WALASharedConfig) getDeploymentName() string {
	return config.Deployment.Service.Name
}

// getDeploymentFQDN returns the FQDN of this deployment.
// The hostname is taken from the 'name' attribute of the 'Deployment' element
// and the domain name is Azure's domain name: 'cloudapp.net'.
func (config *WALASharedConfig) getDeploymentFQDN() string {
	return fmt.Sprintf("%s.cloudapp.net", config.Deployment.Name)
}

// getInternalIP returns the internal IP for this deployment.
// The internalIP is the internal IP of the only instance in this deployment.
func (config *WALASharedConfig) getInternalIP() string {
	return config.Instances[0].Address
}

type WALADeployment struct {
	Name    string                `xml:"name,attr"`
	Service WALADeploymentService `xml:"Service"`
}

type WALADeploymentService struct {
	Name string `xml:"name,attr"`
}

type WALAInstance struct {
	Address string `xml:"address,attr"`
}

// Path to the WALA configuration file.
var _WALAConfigPath = "/var/lib/waagent/SharedConfig.xml"

func parseWALAConfig() (*WALASharedConfig, error) {
	data, err := ioutil.ReadFile(_WALAConfigPath)
	if err != nil {
		return nil, err
	}
	config := &WALASharedConfig{}
	err = xml.Unmarshal(data, config)
	if err != nil {
		return nil, err
	}
	return config, nil
}
<commit_msg>Add comment.<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package azure

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/instance"
	"launchpad.net/loggo"
)

// Register the Azure provider with Juju.
func init() {
	environs.RegisterProvider("azure", azureEnvironProvider{})
}

// Logger for the Azure provider.
var logger = loggo.GetLogger("juju.environs.azure")

type azureEnvironProvider struct{}

// azureEnvironProvider implements EnvironProvider.
var _ environs.EnvironProvider = (*azureEnvironProvider)(nil)

// Open is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
	logger.Debugf("opening environment %q.", cfg.Name())
	// We can't return NewEnviron(cfg) directly here because otherwise,
	// when err is not nil, we end up with a non-nil returned environ and
	// this breaks the loop in cmd/jujud/upgrade.go:run() (see
	// http://golang.org/doc/faq#nil_error for the gory details).
	environ, err := NewEnviron(cfg)
	if err != nil {
		return nil, err
	}
	return environ, nil
}

// PublicAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PublicAddress() (string, error) {
	config, err := parseWALAConfig()
	if err != nil {
		logger.Errorf("error parsing Windows Azure Linux Agent config file (%q): %v", _WALAConfigPath, err)
		return "", err
	}
	return config.getDeploymentFQDN(), nil
}

// PrivateAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PrivateAddress() (string, error) {
	// This returns the instance's *public* address for now.
	// We need to figure out how to do instance-to-instance
	// communication using the private IP before we can use the Azure
	// private address.
	return prov.PublicAddress()
	/*
		config, err := parseWALAConfig()
		if err != nil {
			logger.Errorf("error parsing Windows Azure Linux Agent config file (%q): %v", _WALAConfigPath, err)
			return "", err
		}
		return config.getInternalIP(), nil
	*/
}

// InstanceId is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) InstanceId() (instance.Id, error) {
	config, err := parseWALAConfig()
	if err != nil {
		logger.Errorf("error parsing WALA config file (%q): %v", _WALAConfigPath, err)
		return instance.Id(""), err
	}
	return instance.Id(config.getDeploymentName()), nil
}

// The XML Windows Azure Linux Agent (WALA) is the agent which runs on all
// the Linux Azure VMs.  The hostname of the VM is the service name and the
// juju instanceId is (by design), the deployment's name.
//
// See https://github.com/windows-azure/walinuxagent for more details.
//
// Here is an example content of such a config file:
// <?xml version="1.0" encoding="utf-8"?>
// <SharedConfig version="1.0.0.0" goalStateIncarnation="1">
//   <Deployment name="b6de4c4c7d4a49c39270e0c57481fd9b" guid="{495985a8-8e5a-49aa-826f-d1f7f51045b6}" incarnation="0">
//    <Service name="gwaclmachineex95rsek" guid="{00000000-0000-0000-0000-000000000000}" />
//    <ServiceInstance name="b6de4c4c7d4a49c39270e0c57481fd9b.0" guid="{9806cac7-e566-42b8-9ecb-de8da8f69893}" />
//  [...]
//  <Instances>
//    <Instance id="gwaclroleldc1o5p" address="10.76.200.59">
//      [...]
//    </Instance>
//  </Instances>
//  </Deployment>
// </SharedConfig>

// Structures used to parse the XML Windows Azure Linux Agent (WALA)
// configuration file.

type WALASharedConfig struct {
	XMLName    xml.Name       `xml:"SharedConfig"`
	Deployment WALADeployment `xml:"Deployment"`
	Instances  []WALAInstance `xml:"Instances>Instance"`
}

// getDeploymentName returns the deployment name referenced by the
// configuration.
// Confusingly, this is stored in the 'name' attribute of the 'Service'
// element.
func (config *WALASharedConfig) getDeploymentName() string {
	return config.Deployment.Service.Name
}

// getDeploymentFQDN returns the FQDN of this deployment.
// The hostname is taken from the 'name' attribute of the 'Deployment' element
// and the domain name is Azure's domain name: 'cloudapp.net'.
func (config *WALASharedConfig) getDeploymentFQDN() string {
	return fmt.Sprintf("%s.cloudapp.net", config.Deployment.Name)
}

// getInternalIP returns the internal IP for this deployment.
// The internalIP is the internal IP of the only instance in this deployment.
func (config *WALASharedConfig) getInternalIP() string {
	return config.Instances[0].Address
}

type WALADeployment struct {
	Name    string                `xml:"name,attr"`
	Service WALADeploymentService `xml:"Service"`
}

type WALADeploymentService struct {
	Name string `xml:"name,attr"`
}

type WALAInstance struct {
	Address string `xml:"address,attr"`
}

// Path to the WALA configuration file.
var _WALAConfigPath = "/var/lib/waagent/SharedConfig.xml"

func parseWALAConfig() (*WALASharedConfig, error) {
	data, err := ioutil.ReadFile(_WALAConfigPath)
	if err != nil {
		return nil, err
	}
	config := &WALASharedConfig{}
	err = xml.Unmarshal(data, config)
	if err != nil {
		return nil, err
	}
	return config, nil
}
<|endoftext|>


<commit_before>package app

import (
	"testing"

	fxdebug "github.com/goph/fxt/debug"
	fxlog "github.com/goph/fxt/log"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestNewLoggerConfig(t *testing.T) {
	config := Config{
		Environment: "production",
		LogFormat:   "logfmt",
		Debug:       false,
	}

	expected := fxlog.NewConfig()
	expected.Format = fxlog.LogfmtFormat
	expected.Debug = config.Debug
	expected.Context = []interface{}{
		"environment", config.Environment,
		"service", ServiceName,
		"tag", LogTag,
	}

	actual, err := NewLoggerConfig(config)
	require.NoError(t, err)
	assert.Equal(t, expected, actual)
}

func TestNewDebugConfig(t *testing.T) {
	tests := map[string]struct{
		config Config
		expected *fxdebug.Config
	}{
		"production": {
			Config{
				Environment: "production",
				Debug:       false,
				DebugAddr:   ":10000",
			},
			&fxdebug.Config{
				Network: "tcp",
				Addr:    ":10000",
				Debug:   false,
			},
		},
		"development": {
			Config{
				Environment: "development",
				Debug:       true,
				DebugAddr:   ":10000",
			},
			&fxdebug.Config{
				Network: "tcp",
				Addr:    "127.0.0.1:10000",
				Debug:   true,
			},
		},
		"development_with_interface_specified": {
			Config{
				Environment: "development",
				Debug:       true,
				DebugAddr:   "192.168.0.2:10000",
			},
			&fxdebug.Config{
				Network: "tcp",
				Addr:    "192.168.0.2:10000",
				Debug:   true,
			},
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			actual := NewDebugConfig(test.config)
			assert.Equal(t, test.expected, actual)
		})
	}

}
<commit_msg>Fix cs<commit_after>package app

import (
	"testing"

	fxdebug "github.com/goph/fxt/debug"
	fxlog "github.com/goph/fxt/log"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestNewLoggerConfig(t *testing.T) {
	config := Config{
		Environment: "production",
		LogFormat:   "logfmt",
		Debug:       false,
	}

	expected := fxlog.NewConfig()
	expected.Format = fxlog.LogfmtFormat
	expected.Debug = config.Debug
	expected.Context = []interface{}{
		"environment", config.Environment,
		"service", ServiceName,
		"tag", LogTag,
	}

	actual, err := NewLoggerConfig(config)
	require.NoError(t, err)
	assert.Equal(t, expected, actual)
}

func TestNewDebugConfig(t *testing.T) {
	tests := map[string]struct {
		config   Config
		expected *fxdebug.Config
	}{
		"production": {
			Config{
				Environment: "production",
				Debug:       false,
				DebugAddr:   ":10000",
			},
			&fxdebug.Config{
				Network: "tcp",
				Addr:    ":10000",
				Debug:   false,
			},
		},
		"development": {
			Config{
				Environment: "development",
				Debug:       true,
				DebugAddr:   ":10000",
			},
			&fxdebug.Config{
				Network: "tcp",
				Addr:    "127.0.0.1:10000",
				Debug:   true,
			},
		},
		"development_with_interface_specified": {
			Config{
				Environment: "development",
				Debug:       true,
				DebugAddr:   "192.168.0.2:10000",
			},
			&fxdebug.Config{
				Network: "tcp",
				Addr:    "192.168.0.2:10000",
				Debug:   true,
			},
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			actual := NewDebugConfig(test.config)
			assert.Equal(t, test.expected, actual)
		})
	}

}
<|endoftext|>


<commit_before>package container

import (
	"strings"

	"github.com/docker/engine-api/types/blkiodev"
	"github.com/docker/engine-api/types/strslice"
	"github.com/docker/go-connections/nat"
	"github.com/docker/go-units"
)

// NetworkMode represents the container network stack.
type NetworkMode string

// IsolationLevel represents the isolation level of a container. The supported
// values are platform specific
type IsolationLevel string

// IsDefault indicates the default isolation level of a container. On Linux this
// is the native driver. On Windows, this is a Windows Server Container.
func (i IsolationLevel) IsDefault() bool {
	return strings.ToLower(string(i)) == "default" || string(i) == ""
}

// IpcMode represents the container ipc stack.
type IpcMode string

// IsPrivate indicates whether the container uses it's private ipc stack.
func (n IpcMode) IsPrivate() bool {
	return !(n.IsHost() || n.IsContainer())
}

// IsHost indicates whether the container uses the host's ipc stack.
func (n IpcMode) IsHost() bool {
	return n == "host"
}

// IsContainer indicates whether the container uses a container's ipc stack.
func (n IpcMode) IsContainer() bool {
	parts := strings.SplitN(string(n), ":", 2)
	return len(parts) > 1 && parts[0] == "container"
}

// Valid indicates whether the ipc stack is valid.
func (n IpcMode) Valid() bool {
	parts := strings.Split(string(n), ":")
	switch mode := parts[0]; mode {
	case "", "host":
	case "container":
		if len(parts) != 2 || parts[1] == "" {
			return false
		}
	default:
		return false
	}
	return true
}

// Container returns the name of the container ipc stack is going to be used.
func (n IpcMode) Container() string {
	parts := strings.SplitN(string(n), ":", 2)
	if len(parts) > 1 {
		return parts[1]
	}
	return ""
}

// UTSMode represents the UTS namespace of the container.
type UTSMode string

// IsPrivate indicates whether the container uses it's private UTS namespace.
func (n UTSMode) IsPrivate() bool {
	return !(n.IsHost())
}

// IsHost indicates whether the container uses the host's UTS namespace.
func (n UTSMode) IsHost() bool {
	return n == "host"
}

// Valid indicates whether the UTS namespace is valid.
func (n UTSMode) Valid() bool {
	parts := strings.Split(string(n), ":")
	switch mode := parts[0]; mode {
	case "", "host":
	default:
		return false
	}
	return true
}

// PidMode represents the pid stack of the container.
type PidMode string

// IsPrivate indicates whether the container uses it's private pid stack.
func (n PidMode) IsPrivate() bool {
	return !(n.IsHost())
}

// IsHost indicates whether the container uses the host's pid stack.
func (n PidMode) IsHost() bool {
	return n == "host"
}

// Valid indicates whether the pid stack is valid.
func (n PidMode) Valid() bool {
	parts := strings.Split(string(n), ":")
	switch mode := parts[0]; mode {
	case "", "host":
	default:
		return false
	}
	return true
}

// DeviceMapping represents the device mapping between the host and the container.
type DeviceMapping struct {
	PathOnHost        string
	PathInContainer   string
	CgroupPermissions string
}

// RestartPolicy represents the restart policies of the container.
type RestartPolicy struct {
	Name              string
	MaximumRetryCount int
}

// IsNone indicates whether the container has the "no" restart policy.
// This means the container will not automatically restart when exiting.
func (rp *RestartPolicy) IsNone() bool {
	return rp.Name == "no"
}

// IsAlways indicates whether the container has the "always" restart policy.
// This means the container will automatically restart regardless of the exit status.
func (rp *RestartPolicy) IsAlways() bool {
	return rp.Name == "always"
}

// IsOnFailure indicates whether the container has the "on-failure" restart policy.
// This means the contain will automatically restart of exiting with a non-zero exit status.
func (rp *RestartPolicy) IsOnFailure() bool {
	return rp.Name == "on-failure"
}

// IsUnlessStopped indicates whether the container has the
// "unless-stopped" restart policy. This means the container will
// automatically restart unless user has put it to stopped state.
func (rp *RestartPolicy) IsUnlessStopped() bool {
	return rp.Name == "unless-stopped"
}

// IsSame compares two RestartPolicy to see if they are the same
func (rp *RestartPolicy) IsSame(tp *RestartPolicy) bool {
	return rp.Name == tp.Name && rp.MaximumRetryCount == tp.MaximumRetryCount
}

// LogConfig represents the logging configuration of the container.
type LogConfig struct {
	Type   string
	Config map[string]string
}

// Resources contains container's resources (cgroups config, ulimits...)
type Resources struct {
	// Applicable to all platforms
	CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers)

	// Applicable to UNIX platforms
	CgroupParent         string // Parent cgroup.
	BlkioWeight          uint16 // Block IO weight (relative weight vs. other containers)
	BlkioWeightDevice    []*blkiodev.WeightDevice
	BlkioDeviceReadBps   []*blkiodev.ThrottleDevice
	BlkioDeviceWriteBps  []*blkiodev.ThrottleDevice
	BlkioDeviceReadIOps  []*blkiodev.ThrottleDevice
	BlkioDeviceWriteIOps []*blkiodev.ThrottleDevice
	CPUPeriod            int64           `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period
	CPUQuota             int64           `json:"CpuQuota"`  // CPU CFS (Completely Fair Scheduler) quota
	CpusetCpus           string          // CpusetCpus 0-2, 0,1
	CpusetMems           string          // CpusetMems 0-2, 0,1
	Devices              []DeviceMapping // List of devices to map inside the container
	KernelMemory         int64           // Kernel memory limit (in bytes)
	Memory               int64           // Memory limit (in bytes)
	MemoryReservation    int64           // Memory soft limit (in bytes)
	MemorySwap           int64           // Total memory usage (memory + swap); set `-1` to disable swap
	MemorySwappiness     *int64          // Tuning container memory swappiness behaviour
	OomKillDisable       *bool           // Whether to disable OOM Killer or not
	PidsLimit            int64           // Setting pids limit for a container
	Ulimits              []*units.Ulimit // List of ulimits to be set in the container
}

// UpdateConfig holds the mutable attributes of a Container.
// Those attributes can be updated at runtime.
type UpdateConfig struct {
	// Contains container's resources (cgroups, ulimits)
	Resources
	RestartPolicy RestartPolicy
}

// HostConfig the non-portable Config structure of a container.
// Here, "non-portable" means "dependent of the host we are running on".
// Portable information *should* appear in Config.
type HostConfig struct {
	// Applicable to all platforms
	Binds           []string      // List of volume bindings for this container
	ContainerIDFile string        // File (path) where the containerId is written
	LogConfig       LogConfig     // Configuration of the logs for this container
	NetworkMode     NetworkMode   // Network mode to use for the container
	PortBindings    nat.PortMap   // Port mapping between the exposed port (container) and the host
	RestartPolicy   RestartPolicy // Restart policy to be used for the container
	VolumeDriver    string        // Name of the volume driver used to mount volumes
	VolumesFrom     []string      // List of volumes to take from other container

	// Applicable to UNIX platforms
	CapAdd          *strslice.StrSlice // List of kernel capabilities to add to the container
	CapDrop         *strslice.StrSlice // List of kernel capabilities to remove from the container
	DNS             []string           `json:"Dns"`        // List of DNS server to lookup
	DNSOptions      []string           `json:"DnsOptions"` // List of DNSOption to look for
	DNSSearch       []string           `json:"DnsSearch"`  // List of DNSSearch to look for
	ExtraHosts      []string           // List of extra hosts
	GroupAdd        []string           // List of additional groups that the container process will run as
	IpcMode         IpcMode            // IPC namespace to use for the container
	Links           []string           // List of links (in the name:alias form)
	OomScoreAdj     int                // Container preference for OOM-killing
	PidMode         PidMode            // PID namespace to use for the container
	Privileged      bool               // Is the container in privileged mode
	PublishAllPorts bool               // Should docker publish all exposed port for the container
	ReadonlyRootfs  bool               // Is the container root filesystem in read-only
	SecurityOpt     []string           // List of string values to customize labels for MLS systems, such as SELinux.
	Tmpfs           map[string]string  `json:",omitempty"` // List of tmpfs (mounts) used for the container
	UTSMode         UTSMode            // UTS namespace to use for the container
	ShmSize         int64              // Total shm memory usage

	// Applicable to Windows
	ConsoleSize [2]int         // Initial console size
	Isolation   IsolationLevel // Isolation level of the container (eg default, hyperv)

	// Contains container's resources (cgroups, ulimits)
	Resources
}
<commit_msg>Fix comment of swap limit<commit_after>package container

import (
	"strings"

	"github.com/docker/engine-api/types/blkiodev"
	"github.com/docker/engine-api/types/strslice"
	"github.com/docker/go-connections/nat"
	"github.com/docker/go-units"
)

// NetworkMode represents the container network stack.
type NetworkMode string

// IsolationLevel represents the isolation level of a container. The supported
// values are platform specific
type IsolationLevel string

// IsDefault indicates the default isolation level of a container. On Linux this
// is the native driver. On Windows, this is a Windows Server Container.
func (i IsolationLevel) IsDefault() bool {
	return strings.ToLower(string(i)) == "default" || string(i) == ""
}

// IpcMode represents the container ipc stack.
type IpcMode string

// IsPrivate indicates whether the container uses it's private ipc stack.
func (n IpcMode) IsPrivate() bool {
	return !(n.IsHost() || n.IsContainer())
}

// IsHost indicates whether the container uses the host's ipc stack.
func (n IpcMode) IsHost() bool {
	return n == "host"
}

// IsContainer indicates whether the container uses a container's ipc stack.
func (n IpcMode) IsContainer() bool {
	parts := strings.SplitN(string(n), ":", 2)
	return len(parts) > 1 && parts[0] == "container"
}

// Valid indicates whether the ipc stack is valid.
func (n IpcMode) Valid() bool {
	parts := strings.Split(string(n), ":")
	switch mode := parts[0]; mode {
	case "", "host":
	case "container":
		if len(parts) != 2 || parts[1] == "" {
			return false
		}
	default:
		return false
	}
	return true
}

// Container returns the name of the container ipc stack is going to be used.
func (n IpcMode) Container() string {
	parts := strings.SplitN(string(n), ":", 2)
	if len(parts) > 1 {
		return parts[1]
	}
	return ""
}

// UTSMode represents the UTS namespace of the container.
type UTSMode string

// IsPrivate indicates whether the container uses it's private UTS namespace.
func (n UTSMode) IsPrivate() bool {
	return !(n.IsHost())
}

// IsHost indicates whether the container uses the host's UTS namespace.
func (n UTSMode) IsHost() bool {
	return n == "host"
}

// Valid indicates whether the UTS namespace is valid.
func (n UTSMode) Valid() bool {
	parts := strings.Split(string(n), ":")
	switch mode := parts[0]; mode {
	case "", "host":
	default:
		return false
	}
	return true
}

// PidMode represents the pid stack of the container.
type PidMode string

// IsPrivate indicates whether the container uses it's private pid stack.
func (n PidMode) IsPrivate() bool {
	return !(n.IsHost())
}

// IsHost indicates whether the container uses the host's pid stack.
func (n PidMode) IsHost() bool {
	return n == "host"
}

// Valid indicates whether the pid stack is valid.
func (n PidMode) Valid() bool {
	parts := strings.Split(string(n), ":")
	switch mode := parts[0]; mode {
	case "", "host":
	default:
		return false
	}
	return true
}

// DeviceMapping represents the device mapping between the host and the container.
type DeviceMapping struct {
	PathOnHost        string
	PathInContainer   string
	CgroupPermissions string
}

// RestartPolicy represents the restart policies of the container.
type RestartPolicy struct {
	Name              string
	MaximumRetryCount int
}

// IsNone indicates whether the container has the "no" restart policy.
// This means the container will not automatically restart when exiting.
func (rp *RestartPolicy) IsNone() bool {
	return rp.Name == "no"
}

// IsAlways indicates whether the container has the "always" restart policy.
// This means the container will automatically restart regardless of the exit status.
func (rp *RestartPolicy) IsAlways() bool {
	return rp.Name == "always"
}

// IsOnFailure indicates whether the container has the "on-failure" restart policy.
// This means the contain will automatically restart of exiting with a non-zero exit status.
func (rp *RestartPolicy) IsOnFailure() bool {
	return rp.Name == "on-failure"
}

// IsUnlessStopped indicates whether the container has the
// "unless-stopped" restart policy. This means the container will
// automatically restart unless user has put it to stopped state.
func (rp *RestartPolicy) IsUnlessStopped() bool {
	return rp.Name == "unless-stopped"
}

// IsSame compares two RestartPolicy to see if they are the same
func (rp *RestartPolicy) IsSame(tp *RestartPolicy) bool {
	return rp.Name == tp.Name && rp.MaximumRetryCount == tp.MaximumRetryCount
}

// LogConfig represents the logging configuration of the container.
type LogConfig struct {
	Type   string
	Config map[string]string
}

// Resources contains container's resources (cgroups config, ulimits...)
type Resources struct {
	// Applicable to all platforms
	CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers)

	// Applicable to UNIX platforms
	CgroupParent         string // Parent cgroup.
	BlkioWeight          uint16 // Block IO weight (relative weight vs. other containers)
	BlkioWeightDevice    []*blkiodev.WeightDevice
	BlkioDeviceReadBps   []*blkiodev.ThrottleDevice
	BlkioDeviceWriteBps  []*blkiodev.ThrottleDevice
	BlkioDeviceReadIOps  []*blkiodev.ThrottleDevice
	BlkioDeviceWriteIOps []*blkiodev.ThrottleDevice
	CPUPeriod            int64           `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period
	CPUQuota             int64           `json:"CpuQuota"`  // CPU CFS (Completely Fair Scheduler) quota
	CpusetCpus           string          // CpusetCpus 0-2, 0,1
	CpusetMems           string          // CpusetMems 0-2, 0,1
	Devices              []DeviceMapping // List of devices to map inside the container
	KernelMemory         int64           // Kernel memory limit (in bytes)
	Memory               int64           // Memory limit (in bytes)
	MemoryReservation    int64           // Memory soft limit (in bytes)
	MemorySwap           int64           // Total memory usage (memory + swap); set `-1` to enable unlimited swap
	MemorySwappiness     *int64          // Tuning container memory swappiness behaviour
	OomKillDisable       *bool           // Whether to disable OOM Killer or not
	PidsLimit            int64           // Setting pids limit for a container
	Ulimits              []*units.Ulimit // List of ulimits to be set in the container
}

// UpdateConfig holds the mutable attributes of a Container.
// Those attributes can be updated at runtime.
type UpdateConfig struct {
	// Contains container's resources (cgroups, ulimits)
	Resources
	RestartPolicy RestartPolicy
}

// HostConfig the non-portable Config structure of a container.
// Here, "non-portable" means "dependent of the host we are running on".
// Portable information *should* appear in Config.
type HostConfig struct {
	// Applicable to all platforms
	Binds           []string      // List of volume bindings for this container
	ContainerIDFile string        // File (path) where the containerId is written
	LogConfig       LogConfig     // Configuration of the logs for this container
	NetworkMode     NetworkMode   // Network mode to use for the container
	PortBindings    nat.PortMap   // Port mapping between the exposed port (container) and the host
	RestartPolicy   RestartPolicy // Restart policy to be used for the container
	VolumeDriver    string        // Name of the volume driver used to mount volumes
	VolumesFrom     []string      // List of volumes to take from other container

	// Applicable to UNIX platforms
	CapAdd          *strslice.StrSlice // List of kernel capabilities to add to the container
	CapDrop         *strslice.StrSlice // List of kernel capabilities to remove from the container
	DNS             []string           `json:"Dns"`        // List of DNS server to lookup
	DNSOptions      []string           `json:"DnsOptions"` // List of DNSOption to look for
	DNSSearch       []string           `json:"DnsSearch"`  // List of DNSSearch to look for
	ExtraHosts      []string           // List of extra hosts
	GroupAdd        []string           // List of additional groups that the container process will run as
	IpcMode         IpcMode            // IPC namespace to use for the container
	Links           []string           // List of links (in the name:alias form)
	OomScoreAdj     int                // Container preference for OOM-killing
	PidMode         PidMode            // PID namespace to use for the container
	Privileged      bool               // Is the container in privileged mode
	PublishAllPorts bool               // Should docker publish all exposed port for the container
	ReadonlyRootfs  bool               // Is the container root filesystem in read-only
	SecurityOpt     []string           // List of string values to customize labels for MLS systems, such as SELinux.
	Tmpfs           map[string]string  `json:",omitempty"` // List of tmpfs (mounts) used for the container
	UTSMode         UTSMode            // UTS namespace to use for the container
	ShmSize         int64              // Total shm memory usage

	// Applicable to Windows
	ConsoleSize [2]int         // Initial console size
	Isolation   IsolationLevel // Isolation level of the container (eg default, hyperv)

	// Contains container's resources (cgroups, ulimits)
	Resources
}
<|endoftext|>


<commit_before>package dnsr

import (
	"fmt"
	"io"
	"strings"
	"sync"
	"time"

	lru "github.com/hashicorp/golang-lru"
	"github.com/miekg/dns"
)

//go:generate sh generate.sh

var (
	Root           *Resolver
	DebugLogger    io.Writer
	Timeout        = 1000 * time.Millisecond
	MaxRecursion   = 10
	MaxNameservers = 2
	MaxIPs         = 2
)

func init() {
	Root = New(strings.Count(root, "\n"))
	for t := range dns.ParseZone(strings.NewReader(root), "", "") {
		if t.Error == nil {
			Root.saveDNSRR(t.RR)
		}
	}
}

// Resolver implements a primitive, non-recursive, caching DNS resolver.
type Resolver struct {
	cache  *lru.Cache
	client *dns.Client
}

// New initializes a Resolver with the specified cache size. Cache size defaults to 10,000 if size <= 0.
func New(size int) *Resolver {
	if size <= 0 {
		size = 10000
	}
	cache, _ := lru.New(size)
	r := &Resolver{
		client: &dns.Client{
			DialTimeout:  Timeout,
			ReadTimeout:  Timeout,
			WriteTimeout: Timeout,
		},
		cache: cache,
	}
	return r
}

// Resolve finds DNS records of type qtype for the domain qname. It returns a slice of *RR.
// For nonexistent domains (where a DNS server will return NXDOMAIN), it will return an empty, non-nil slice.
// Specify an empty string in qtype to receive any DNS records found (currently A, AAAA, NS, CNAME, and TXT).
func (r *Resolver) Resolve(qname string, qtype string) []*RR {
	return r.resolve(qname, qtype, 0)
}

func (r *Resolver) resolve(qname string, qtype string, depth int) []*RR {
	if depth++; depth > MaxRecursion {
		logMaxRecursion(qname, qtype, depth)
		return nil
	}
	qname = toLowerFQDN(qname)
	if rrs := r.cacheGet(qname, qtype); rrs != nil {
		return rrs
	}
	logResolveStart(qname, qtype, depth)
	defer logResolveEnd(qname, qtype, depth, time.Now())
	return r.resolveNS(qname, qtype, depth)
}

func (r *Resolver) resolveNS(qname string, qtype string, depth int) []*RR {
	success := make(chan bool, 1)
	for pname, ok := qname, true; ok; pname, ok = parent(pname) {
		if pname == qname && qtype == "NS" { // If we’re looking for [foo.com,NS], then skip to [com,NS]
			continue
		}

		// Query all DNS servers in parallel
		count := 0
		for _, nrr := range r.resolve(pname, "NS", depth) {
			if qtype != "" { // Early out for specific queries
				if rrs := r.cacheGet(qname, qtype); rrs != nil {
					return rrs
				}
			}
			if nrr.Type != "NS" {
				continue
			}
			if count++; count > MaxNameservers {
				break
			}
			go r.exchange(success, nrr.Value, qname, qtype, depth)
		}

		// Wait for first response
		if count > 0 {
			select {
			case <-success:
				return r.resolveCNAMEs(qname, qtype, depth)
			case <-time.After(Timeout):
				continue
			}
		}
	}
	return nil
}

func (r *Resolver) exchange(success chan<- bool, host string, qname string, qtype string, depth int) {
	dtype := dns.StringToType[qtype]
	if dtype == 0 {
		dtype = dns.TypeA
	}
	qmsg := &dns.Msg{}
	qmsg.SetQuestion(qname, dtype)
	qmsg.MsgHdr.RecursionDesired = false

	// Find each A record for the DNS server
	count := 0
	for _, rr := range r.resolve(host, "A", depth) {
		if rr.Type != "A" { // FIXME: support AAAA records?
			continue
		}

		// Never query more than MaxIPs for any nameserver
		if count++; count > MaxIPs {
			return
		}

		// Synchronously query this DNS server
		start := time.Now()
		rmsg, _, err := r.client.Exchange(qmsg, rr.Value+":53")
		logExchange(rr.Value, qmsg, depth, start, err)
		if err != nil {
			continue
		}

		// FIXME: cache NXDOMAIN responses responsibly
		if rmsg.Rcode == dns.RcodeNameError {
			r.cacheAdd(qname, nil)
		}

		// If successful, cache the results
		r.saveDNSRR(rmsg.Answer...)
		r.saveDNSRR(rmsg.Ns...)
		r.saveDNSRR(rmsg.Extra...)

		// Never block
		select {
		case success <- true:
		default:
		}

		// Return after first successful network request
		return
	}
}

func (r *Resolver) resolveCNAMEs(qname string, qtype string, depth int) []*RR {
	rrs := []*RR{} // Return non-nil slice indicating difference between NXDOMAIN and an error
	for _, crr := range r.cacheGet(qname, "") {
		rrs = append(rrs, crr)
		if crr.Type != "CNAME" {
			continue
		}
		logCNAME(depth, crr.String())
		for _, rr := range r.resolve(crr.Value, qtype, depth) {
			r.cacheAdd(qname, rr)
			rrs = append(rrs, crr)
		}
	}
	return rrs
}

func parent(name string) (string, bool) {
	labels := dns.SplitDomainName(name)
	if labels == nil {
		return "", false
	}
	return toLowerFQDN(strings.Join(labels[1:], ".")), true
}

func toLowerFQDN(name string) string {
	return dns.Fqdn(strings.ToLower(name))
}

func logMaxRecursion(qname string, qtype string, depth int) {
	fmt.Printf("%s Error: MAX RECURSION @ %s %s %d\n",
		strings.Repeat("│   ", depth-1), qname, qtype, depth)
}

func logResolveStart(qname string, qtype string, depth int) {
	if DebugLogger == nil {
		return
	}
	fmt.Fprintf(DebugLogger, "%s┌─── resolve(\"%s\", \"%s\", %d)\n",
		strings.Repeat("│   ", depth-1), qname, qtype, depth)
}

func logResolveEnd(qname string, qtype string, depth int, start time.Time) {
	if DebugLogger == nil {
		return
	}
	dur := time.Since(start)
	fmt.Fprintf(DebugLogger, "%s└─── %dms: resolve(\"%s\", \"%s\", %d)\n",
		strings.Repeat("│   ", depth-1), dur/time.Millisecond, qname, qtype, depth)
}

func logCNAME(depth int, cname string) {
	if DebugLogger == nil {
		return
	}
	fmt.Fprintf(DebugLogger, "%s│    CNAME: %s\n", strings.Repeat("│   ", depth-1), cname)
}

func logExchange(host string, qmsg *dns.Msg, depth int, start time.Time, err error) {
	if DebugLogger == nil {
		return
	}
	dur := time.Since(start)
	fmt.Fprintf(DebugLogger, "%s│    %dms: dig @%s %s %s\n",
		strings.Repeat("│   ", depth-1), dur/time.Millisecond, host, qmsg.Question[0].Name, dns.TypeToString[qmsg.Question[0].Qtype])
	if err != nil {
		fmt.Fprintf(DebugLogger, "%s│    %dms: ERROR: %s\n",
			strings.Repeat("│   ", depth-1), dur/time.Millisecond, err.Error())
	}
}

// RR represents a DNS resource record.
type RR struct {
	Name  string
	Type  string
	Value string
}

// String returns a string representation of an RR in zone-file format.
func (rr *RR) String() string {
	return rr.Name + "\t      3600\tIN\t" + rr.Type + "\t" + rr.Value
}

func convertRR(drr dns.RR) *RR {
	switch t := drr.(type) {
	case *dns.NS:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Ns}
	case *dns.CNAME:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Target}
	case *dns.A:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.A.String()}
	case *dns.AAAA:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.AAAA.String()}
	case *dns.TXT:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], strings.Join(t.Txt, "\t")}
	default:
		// fmt.Printf("%s\n", drr.String())
	}
	return nil
}

type key struct {
	Name string
	Type string
}

type entry struct {
	m   sync.RWMutex
	rrs map[RR]struct{}
}

// saveDNSRR saves 1 or more DNS records to the resolver cache.
func (r *Resolver) saveDNSRR(drrs ...dns.RR) {
	for _, drr := range drrs {
		if rr := convertRR(drr); rr != nil {
			r.cacheAdd(rr.Name, rr)
		}
	}
}

// cacheAdd adds 0 or more DNS records to the resolver cache for a specific
// domain name and record type. This ensures the cache entry exists, even
// if empty, for NXDOMAIN responses.
func (r *Resolver) cacheAdd(qname string, rr *RR) {
	qname = toLowerFQDN(qname)
	e := r.getEntry(qname)
	if e == nil {
		e = &entry{rrs: make(map[RR]struct{}, 0)}
		e.m.Lock()
		r.cache.Add(qname, e)
	} else {
		e.m.Lock()
	}
	defer e.m.Unlock()
	if rr != nil {
		e.rrs[*rr] = struct{}{}
	}
}

// cacheGet returns a randomly ordered slice of DNS records.
func (r *Resolver) cacheGet(qname string, qtype string) []*RR {
	e := r.getEntry(qname)
	if e == nil && r != Root {
		e = Root.getEntry(qname)
	}
	if e == nil {
		return nil
	}
	e.m.RLock()
	defer e.m.RUnlock()
	if len(e.rrs) == 0 {
		return []*RR{}
	}
	rrs := make([]*RR, 0, len(e.rrs))
	for rr, _ := range e.rrs {
		// fmt.Printf("%s\n", rr.String())
		if qtype == "" || rr.Type == qtype {
			rrs = append(rrs, &RR{rr.Name, rr.Type, rr.Value})
		}
	}
	if len(rrs) == 0 && (qtype != "" && qtype != "NS") {
		return nil
	}
	return rrs
}

// getEntry returns a single cache entry or nil if an entry does not exist in the cache.
func (r *Resolver) getEntry(qname string) *entry {
	c, ok := r.cache.Get(qname)
	if !ok {
		return nil
	}
	e, ok := c.(*entry)
	if !ok {
		return nil
	}
	return e
}
<commit_msg>Do not log recursion errors to stdout<commit_after>package dnsr

import (
	"fmt"
	"io"
	"strings"
	"sync"
	"time"

	lru "github.com/hashicorp/golang-lru"
	"github.com/miekg/dns"
)

//go:generate sh generate.sh

var (
	Root           *Resolver
	DebugLogger    io.Writer
	Timeout        = 1000 * time.Millisecond
	MaxRecursion   = 10
	MaxNameservers = 2
	MaxIPs         = 2
)

func init() {
	Root = New(strings.Count(root, "\n"))
	for t := range dns.ParseZone(strings.NewReader(root), "", "") {
		if t.Error == nil {
			Root.saveDNSRR(t.RR)
		}
	}
}

// Resolver implements a primitive, non-recursive, caching DNS resolver.
type Resolver struct {
	cache  *lru.Cache
	client *dns.Client
}

// New initializes a Resolver with the specified cache size. Cache size defaults to 10,000 if size <= 0.
func New(size int) *Resolver {
	if size <= 0 {
		size = 10000
	}
	cache, _ := lru.New(size)
	r := &Resolver{
		client: &dns.Client{
			DialTimeout:  Timeout,
			ReadTimeout:  Timeout,
			WriteTimeout: Timeout,
		},
		cache: cache,
	}
	return r
}

// Resolve finds DNS records of type qtype for the domain qname. It returns a slice of *RR.
// For nonexistent domains (where a DNS server will return NXDOMAIN), it will return an empty, non-nil slice.
// Specify an empty string in qtype to receive any DNS records found (currently A, AAAA, NS, CNAME, and TXT).
func (r *Resolver) Resolve(qname string, qtype string) []*RR {
	return r.resolve(qname, qtype, 0)
}

func (r *Resolver) resolve(qname string, qtype string, depth int) []*RR {
	if depth++; depth > MaxRecursion {
		logMaxRecursion(qname, qtype, depth)
		return nil
	}
	qname = toLowerFQDN(qname)
	if rrs := r.cacheGet(qname, qtype); rrs != nil {
		return rrs
	}
	logResolveStart(qname, qtype, depth)
	defer logResolveEnd(qname, qtype, depth, time.Now())
	return r.resolveNS(qname, qtype, depth)
}

func (r *Resolver) resolveNS(qname string, qtype string, depth int) []*RR {
	success := make(chan bool, 1)
	for pname, ok := qname, true; ok; pname, ok = parent(pname) {
		if pname == qname && qtype == "NS" { // If we’re looking for [foo.com,NS], then skip to [com,NS]
			continue
		}

		// Query all DNS servers in parallel
		count := 0
		for _, nrr := range r.resolve(pname, "NS", depth) {
			if qtype != "" { // Early out for specific queries
				if rrs := r.cacheGet(qname, qtype); rrs != nil {
					return rrs
				}
			}
			if nrr.Type != "NS" {
				continue
			}
			if count++; count > MaxNameservers {
				break
			}
			go r.exchange(success, nrr.Value, qname, qtype, depth)
		}

		// Wait for first response
		if count > 0 {
			select {
			case <-success:
				return r.resolveCNAMEs(qname, qtype, depth)
			case <-time.After(Timeout):
				continue
			}
		}
	}
	return nil
}

func (r *Resolver) exchange(success chan<- bool, host string, qname string, qtype string, depth int) {
	dtype := dns.StringToType[qtype]
	if dtype == 0 {
		dtype = dns.TypeA
	}
	qmsg := &dns.Msg{}
	qmsg.SetQuestion(qname, dtype)
	qmsg.MsgHdr.RecursionDesired = false

	// Find each A record for the DNS server
	count := 0
	for _, rr := range r.resolve(host, "A", depth) {
		if rr.Type != "A" { // FIXME: support AAAA records?
			continue
		}

		// Never query more than MaxIPs for any nameserver
		if count++; count > MaxIPs {
			return
		}

		// Synchronously query this DNS server
		start := time.Now()
		rmsg, _, err := r.client.Exchange(qmsg, rr.Value+":53")
		logExchange(rr.Value, qmsg, depth, start, err)
		if err != nil {
			continue
		}

		// FIXME: cache NXDOMAIN responses responsibly
		if rmsg.Rcode == dns.RcodeNameError {
			r.cacheAdd(qname, nil)
		}

		// If successful, cache the results
		r.saveDNSRR(rmsg.Answer...)
		r.saveDNSRR(rmsg.Ns...)
		r.saveDNSRR(rmsg.Extra...)

		// Never block
		select {
		case success <- true:
		default:
		}

		// Return after first successful network request
		return
	}
}

func (r *Resolver) resolveCNAMEs(qname string, qtype string, depth int) []*RR {
	rrs := []*RR{} // Return non-nil slice indicating difference between NXDOMAIN and an error
	for _, crr := range r.cacheGet(qname, "") {
		rrs = append(rrs, crr)
		if crr.Type != "CNAME" {
			continue
		}
		logCNAME(depth, crr.String())
		for _, rr := range r.resolve(crr.Value, qtype, depth) {
			r.cacheAdd(qname, rr)
			rrs = append(rrs, crr)
		}
	}
	return rrs
}

func parent(name string) (string, bool) {
	labels := dns.SplitDomainName(name)
	if labels == nil {
		return "", false
	}
	return toLowerFQDN(strings.Join(labels[1:], ".")), true
}

func toLowerFQDN(name string) string {
	return dns.Fqdn(strings.ToLower(name))
}

func logMaxRecursion(qname string, qtype string, depth int) {
	if DebugLogger == nil {
		return
	}
	fmt.Fprintf(DebugLogger, "%s Error: MAX RECURSION @ %s %s %d\n",
		strings.Repeat("│   ", depth-1), qname, qtype, depth)
}

func logResolveStart(qname string, qtype string, depth int) {
	if DebugLogger == nil {
		return
	}
	fmt.Fprintf(DebugLogger, "%s┌─── resolve(\"%s\", \"%s\", %d)\n",
		strings.Repeat("│   ", depth-1), qname, qtype, depth)
}

func logResolveEnd(qname string, qtype string, depth int, start time.Time) {
	if DebugLogger == nil {
		return
	}
	dur := time.Since(start)
	fmt.Fprintf(DebugLogger, "%s└─── %dms: resolve(\"%s\", \"%s\", %d)\n",
		strings.Repeat("│   ", depth-1), dur/time.Millisecond, qname, qtype, depth)
}

func logCNAME(depth int, cname string) {
	if DebugLogger == nil {
		return
	}
	fmt.Fprintf(DebugLogger, "%s│    CNAME: %s\n", strings.Repeat("│   ", depth-1), cname)
}

func logExchange(host string, qmsg *dns.Msg, depth int, start time.Time, err error) {
	if DebugLogger == nil {
		return
	}
	dur := time.Since(start)
	fmt.Fprintf(DebugLogger, "%s│    %dms: dig @%s %s %s\n",
		strings.Repeat("│   ", depth-1), dur/time.Millisecond, host, qmsg.Question[0].Name, dns.TypeToString[qmsg.Question[0].Qtype])
	if err != nil {
		fmt.Fprintf(DebugLogger, "%s│    %dms: ERROR: %s\n",
			strings.Repeat("│   ", depth-1), dur/time.Millisecond, err.Error())
	}
}

// RR represents a DNS resource record.
type RR struct {
	Name  string
	Type  string
	Value string
}

// String returns a string representation of an RR in zone-file format.
func (rr *RR) String() string {
	return rr.Name + "\t      3600\tIN\t" + rr.Type + "\t" + rr.Value
}

func convertRR(drr dns.RR) *RR {
	switch t := drr.(type) {
	case *dns.NS:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Ns}
	case *dns.CNAME:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Target}
	case *dns.A:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.A.String()}
	case *dns.AAAA:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.AAAA.String()}
	case *dns.TXT:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], strings.Join(t.Txt, "\t")}
	default:
		// fmt.Printf("%s\n", drr.String())
	}
	return nil
}

type key struct {
	Name string
	Type string
}

type entry struct {
	m   sync.RWMutex
	rrs map[RR]struct{}
}

// saveDNSRR saves 1 or more DNS records to the resolver cache.
func (r *Resolver) saveDNSRR(drrs ...dns.RR) {
	for _, drr := range drrs {
		if rr := convertRR(drr); rr != nil {
			r.cacheAdd(rr.Name, rr)
		}
	}
}

// cacheAdd adds 0 or more DNS records to the resolver cache for a specific
// domain name and record type. This ensures the cache entry exists, even
// if empty, for NXDOMAIN responses.
func (r *Resolver) cacheAdd(qname string, rr *RR) {
	qname = toLowerFQDN(qname)
	e := r.getEntry(qname)
	if e == nil {
		e = &entry{rrs: make(map[RR]struct{}, 0)}
		e.m.Lock()
		r.cache.Add(qname, e)
	} else {
		e.m.Lock()
	}
	defer e.m.Unlock()
	if rr != nil {
		e.rrs[*rr] = struct{}{}
	}
}

// cacheGet returns a randomly ordered slice of DNS records.
func (r *Resolver) cacheGet(qname string, qtype string) []*RR {
	e := r.getEntry(qname)
	if e == nil && r != Root {
		e = Root.getEntry(qname)
	}
	if e == nil {
		return nil
	}
	e.m.RLock()
	defer e.m.RUnlock()
	if len(e.rrs) == 0 {
		return []*RR{}
	}
	rrs := make([]*RR, 0, len(e.rrs))
	for rr, _ := range e.rrs {
		// fmt.Printf("%s\n", rr.String())
		if qtype == "" || rr.Type == qtype {
			rrs = append(rrs, &RR{rr.Name, rr.Type, rr.Value})
		}
	}
	if len(rrs) == 0 && (qtype != "" && qtype != "NS") {
		return nil
	}
	return rrs
}

// getEntry returns a single cache entry or nil if an entry does not exist in the cache.
func (r *Resolver) getEntry(qname string) *entry {
	c, ok := r.cache.Get(qname)
	if !ok {
		return nil
	}
	e, ok := c.(*entry)
	if !ok {
		return nil
	}
	return e
}
<|endoftext|>


<commit_before>package doit

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"

	"github.com/Sirupsen/logrus"
	"github.com/digitalocean/godo"
	"github.com/docker/docker/pkg/term"
	"github.com/spf13/viper"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
	"golang.org/x/oauth2"
)

var (
	// DoitConfig holds the app's current configuration.
	DoitConfig Config = &LiveConfig{}
)

// Config is an interface that represent doit's config.
type Config interface {
	GetGodoClient() *godo.Client
	SSH(user, host, keyPath string, port int) error
	Set(ns, key string, val interface{})
	GetString(ns, key string) string
	GetBool(ns, key string) bool
	GetInt(ns, key string) int
	GetStringSlice(ns, key string) []string
}

// LiveConfig is an implementation of Config for live values.
type LiveConfig struct{}

var _ Config = &LiveConfig{}

// GetGodoClient returns a GodoClient.
func (c *LiveConfig) GetGodoClient() *godo.Client {
	token := viper.GetString("access-token")
	tokenSource := &TokenSource{AccessToken: token}
	oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
	return godo.NewClient(oauthClient)
}

func sshConnect(user string, host string, method ssh.AuthMethod) (err error) {
	sshc := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{method},
	}
	conn, err := ssh.Dial("tcp", host, sshc)
	if err != nil {
		return err
	}

	session, err := conn.NewSession()
	if err != nil {
		return err
	}

	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	session.Stdin = os.Stdin

	defer session.Close()

	modes := ssh.TerminalModes{
		ssh.ECHO: 1,
	}

	var (
		termWidth, termHeight int
	)
	fd := os.Stdin.Fd()
	if term.IsTerminal(fd) {
		oldState, err := term.MakeRaw(fd)
		if err != nil {
			return err
		}

		defer term.RestoreTerminal(fd, oldState)

		winsize, err := term.GetWinsize(fd)
		if err != nil {
			termWidth = 80
			termHeight = 24
		} else {
			termWidth = int(winsize.Width)
			termHeight = int(winsize.Height)
		}
	}

	if err := session.RequestPty("xterm", termWidth, termHeight, modes); err != nil {
		session.Close()
		return err
	}
	if err == nil {
		err = session.Shell()
	}
	if err != nil {
		return err
	}

	err = session.Wait()
	if err != nil && err != io.EOF {
		// Ignore the error if it's an ExitError with an empty message,
		// this occurs when you do CTRL+c and then run exit cmd which isn't an
		// actual error.
		waitMsg, ok := err.(*ssh.ExitError)
		if ok && waitMsg.Msg() == "" {
			return nil
		}

		return err
	}
	return err
}

// SSH creates a ssh connection to a host.
func (c *LiveConfig) SSH(user, host, keyPath string, port int) (err error) {
	logrus.WithFields(logrus.Fields{
		"user": user,
		"host": host,
	}).Info("ssh")

	sshHost := fmt.Sprintf("%s:%d", host, port)

	// Key Auth
	key, err := ioutil.ReadFile(keyPath)
	if err != nil {
		return err
	}
	privateKey, err := ssh.ParsePrivateKey(key)
	if err != nil {
		return err
	}
	if err := sshConnect(user, sshHost, ssh.PublicKeys(privateKey)); err != nil {
		// Password Auth if Key Auth Fails
		fd := os.Stdin.Fd()
		state, err := terminal.MakeRaw(int(fd))
		if err != nil {
			return err
		}
		defer terminal.Restore(int(fd), state)
		t := terminal.NewTerminal(os.Stdout, ">")
		password, err := t.ReadPassword("Password: ")
		if err != nil {
			return err
		}
		if err := sshConnect(user, sshHost, ssh.Password(string(password))); err != nil {
			return err
		}
	}
	return err
}

// Set sets a config key.
func (c *LiveConfig) Set(ns, key string, val interface{}) {
	nskey := fmt.Sprintf("%s-%s", ns, key)
	viper.Set(nskey, val)
}

// GetString returns a config value as a string.
func (c *LiveConfig) GetString(ns, key string) string {
	if ns == NSRoot {
		return viper.GetString(key)
	}

	nskey := fmt.Sprintf("%s-%s", ns, key)
	return viper.GetString(nskey)
}

// GetBool returns a config value as a bool.
func (c *LiveConfig) GetBool(ns, key string) bool {
	if ns == NSRoot {
		return viper.GetBool(key)
	}

	nskey := fmt.Sprintf("%s-%s", ns, key)
	return viper.GetBool(nskey)
}

// GetInt returns a config value as an int.
func (c *LiveConfig) GetInt(ns, key string) int {
	if ns == NSRoot {
		return viper.GetInt(key)
	}

	nskey := fmt.Sprintf("%s-%s", ns, key)
	return viper.GetInt(nskey)
}

// GetStringSlice returns a config value as a string slice.
func (c *LiveConfig) GetStringSlice(ns, key string) []string {
	if ns == NSRoot {
		return viper.GetStringSlice(key)
	}

	nskey := fmt.Sprintf("%s-%s", ns, key)
	return viper.GetStringSlice(nskey)
}
<commit_msg>cleaned up code<commit_after>package doit

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"

	"github.com/Sirupsen/logrus"
	"github.com/digitalocean/godo"
	"github.com/docker/docker/pkg/term"
	"github.com/spf13/viper"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
	"golang.org/x/oauth2"
)

var (
	// DoitConfig holds the app's current configuration.
	DoitConfig Config = &LiveConfig{}
)

// Config is an interface that represent doit's config.
type Config interface {
	GetGodoClient() *godo.Client
	SSH(user, host, keyPath string, port int) error
	Set(ns, key string, val interface{})
	GetString(ns, key string) string
	GetBool(ns, key string) bool
	GetInt(ns, key string) int
	GetStringSlice(ns, key string) []string
}

// LiveConfig is an implementation of Config for live values.
type LiveConfig struct{}

var _ Config = &LiveConfig{}

// GetGodoClient returns a GodoClient.
func (c *LiveConfig) GetGodoClient() *godo.Client {
	token := viper.GetString("access-token")
	tokenSource := &TokenSource{AccessToken: token}
	oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
	return godo.NewClient(oauthClient)
}

func sshConnect(user string, host string, method ssh.AuthMethod) error {
	sshc := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{method},
	}
	conn, err := ssh.Dial("tcp", host, sshc)
	if err != nil {
		return err
	}
	defer conn.Close()

	session, err := conn.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	session.Stdin = os.Stdin

	var (
		termWidth, termHeight int
	)
	fd := os.Stdin.Fd()

	oldState, err := term.MakeRaw(fd)
	if err != nil {
		return err
	}
	defer term.RestoreTerminal(fd, oldState)

	winsize, err := term.GetWinsize(fd)
	if err != nil {
		termWidth = 80
		termHeight = 24
	} else {
		termWidth = int(winsize.Width)
		termHeight = int(winsize.Height)
	}

	modes := ssh.TerminalModes{
		ssh.ECHO: 1,
	}

	if err := session.RequestPty("xterm", termWidth, termHeight, modes); err != nil {
		return err
	}
	if err == nil {
		err = session.Shell()
	}
	if err != nil {
		return err
	}

	err = session.Wait()
	if serr, ok := err.(*ssh.ExitError); ok && waitMsg.Msg() == "" {
		return nil
	}
	if err == io.EOF {
		return nil
	}
	return err
}

// SSH creates a ssh connection to a host.
func (c *LiveConfig) SSH(user, host, keyPath string, port int) (err error) {
	logrus.WithFields(logrus.Fields{
		"user": user,
		"host": host,
	}).Info("ssh")

	sshHost := fmt.Sprintf("%s:%d", host, port)

	// Key Auth
	key, err := ioutil.ReadFile(keyPath)
	if err != nil {
		return err
	}
	privateKey, err := ssh.ParsePrivateKey(key)
	if err != nil {
		return err
	}
	if err := sshConnect(user, sshHost, ssh.PublicKeys(privateKey)); err != nil {
		// Password Auth if Key Auth Fails
		fd := os.Stdin.Fd()
		state, err := terminal.MakeRaw(int(fd))
		if err != nil {
			return err
		}
		defer terminal.Restore(int(fd), state)
		t := terminal.NewTerminal(os.Stdout, ">")
		password, err := t.ReadPassword("Password: ")
		if err != nil {
			return err
		}
		if err := sshConnect(user, sshHost, ssh.Password(string(password))); err != nil {
			return err
		}
	}
	return err
}

// Set sets a config key.
func (c *LiveConfig) Set(ns, key string, val interface{}) {
	nskey := fmt.Sprintf("%s-%s", ns, key)
	viper.Set(nskey, val)
}

// GetString returns a config value as a string.
func (c *LiveConfig) GetString(ns, key string) string {
	if ns == NSRoot {
		return viper.GetString(key)
	}

	nskey := fmt.Sprintf("%s-%s", ns, key)
	return viper.GetString(nskey)
}

// GetBool returns a config value as a bool.
func (c *LiveConfig) GetBool(ns, key string) bool {
	if ns == NSRoot {
		return viper.GetBool(key)
	}

	nskey := fmt.Sprintf("%s-%s", ns, key)
	return viper.GetBool(nskey)
}

// GetInt returns a config value as an int.
func (c *LiveConfig) GetInt(ns, key string) int {
	if ns == NSRoot {
		return viper.GetInt(key)
	}

	nskey := fmt.Sprintf("%s-%s", ns, key)
	return viper.GetInt(nskey)
}

// GetStringSlice returns a config value as a string slice.
func (c *LiveConfig) GetStringSlice(ns, key string) []string {
	if ns == NSRoot {
		return viper.GetStringSlice(key)
	}

	nskey := fmt.Sprintf("%s-%s", ns, key)
	return viper.GetStringSlice(nskey)
}
<|endoftext|>


<commit_before>package messenger

import (
	"bytes"
	"encoding/gob"
	"net/http"
)

type sessionDump struct {
	FBCookies   []*http.Cookie
	EdgeCookies []*http.Cookie
}

// DumpSession dumps the session (i.e. cookies) and returns it as a []byte.
// Note that if you restore the session, you may not need to login, but you
// must reconnect to chat.
func (s *Session) DumpSession() ([]byte, error) {
	s.requestMutex.RUnlock()
	defer s.requestMutex.RLock()

	fbCookies := s.client.Jar.Cookies(fbURL)
	edgeCookies := s.client.Jar.Cookies(edgeURL)

	buf := new(bytes.Buffer)
	enc := gob.NewEncoder(buf)
	err := enc.Encode(sessionDump{
		FBCookies:   fbCookies,
		EdgeCookies: edgeCookies,
	})
	if err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

// RestoreSession restores the session (i.e. cookies) stored as a []byte
// back into the session. Note that you may not need to login again, but you
// must reconnect to chat.
func (s *Session) RestoreSession(data []byte) error {
	s.requestMutex.Lock()
	defer s.requestMutex.Unlock()

	buf := bytes.NewReader(data)
	dec := gob.NewDecoder(buf)
	restoredSession := sessionDump{}
	err := dec.Decode(&restoredSession)
	if err != nil {
		return err
	}

	s.client.Jar.SetCookies(fbURL, restoredSession.FBCookies)
	s.client.Jar.SetCookies(edgeURL, restoredSession.EdgeCookies)

	return nil
}

func init() {
	gob.Register(sessionDump{})
}
<commit_msg>Fix mutex on session dump<commit_after>package messenger

import (
	"bytes"
	"encoding/gob"
	"net/http"
)

type sessionDump struct {
	FBCookies   []*http.Cookie
	EdgeCookies []*http.Cookie
}

// DumpSession dumps the session (i.e. cookies) and returns it as a []byte.
// Note that if you restore the session, you may not need to login, but you
// must reconnect to chat.
func (s *Session) DumpSession() ([]byte, error) {
	s.requestMutex.RLock()
	defer s.requestMutex.RUnlock()

	fbCookies := s.client.Jar.Cookies(fbURL)
	edgeCookies := s.client.Jar.Cookies(edgeURL)

	buf := new(bytes.Buffer)
	enc := gob.NewEncoder(buf)
	err := enc.Encode(sessionDump{
		FBCookies:   fbCookies,
		EdgeCookies: edgeCookies,
	})
	if err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

// RestoreSession restores the session (i.e. cookies) stored as a []byte
// back into the session. Note that you may not need to login again, but you
// must reconnect to chat.
func (s *Session) RestoreSession(data []byte) error {
	s.requestMutex.Lock()
	defer s.requestMutex.Unlock()

	buf := bytes.NewReader(data)
	dec := gob.NewDecoder(buf)
	restoredSession := sessionDump{}
	err := dec.Decode(&restoredSession)
	if err != nil {
		return err
	}

	s.client.Jar.SetCookies(fbURL, restoredSession.FBCookies)
	s.client.Jar.SetCookies(edgeURL, restoredSession.EdgeCookies)

	return nil
}

func init() {
	gob.Register(sessionDump{})
}
<|endoftext|>


<commit_before>package test

import (
	"fmt"
	"testing"
	"time"

	peer "github.com/libp2p/go-libp2p-peer"
	pt "github.com/libp2p/go-libp2p-peer/test"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	ma "github.com/multiformats/go-multiaddr"
)

var addressBookSuite = map[string]func(book pstore.AddrBook) func(*testing.T){
	"AddAddress":           testAddAddress,
	"Clear":                testClearWorks,
	"SetNegativeTTLClears": testSetNegativeTTLClears,
	"UpdateTTLs":           testUpdateTTLs,
	"NilAddrsDontBreak":    testNilAddrsDontBreak,
	"AddressesExpire":      testAddressesExpire,
	"ClearWithIter":        testClearWithIterator,
	"PeersWithAddresses":   testPeersWithAddrs,
}

type AddrBookFactory func() (pstore.AddrBook, func())

func TestAddrBook(t *testing.T, factory AddrBookFactory) {
	for name, test := range addressBookSuite {
		// Create a new peerstore.
		ab, closeFunc := factory()

		// Run the test.
		t.Run(name, test(ab))

		// Cleanup.
		if closeFunc != nil {
			closeFunc()
		}
	}
}

func generateAddrs(count int) []ma.Multiaddr {
	var addrs = make([]ma.Multiaddr, count)
	for i := 0; i < count; i++ {
		addrs[i] = multiaddr(fmt.Sprintf("/ip4/1.1.1.%d/tcp/1111", i))
	}
	return addrs
}

func generatePeerIds(count int) []peer.ID {
	var ids = make([]peer.ID, count)
	for i := 0; i < count; i++ {
		ids[i], _ = pt.RandPeerID()
	}
	return ids
}

func testAddAddress(ab pstore.AddrBook) func(*testing.T) {
	return func(t *testing.T) {
		t.Run("add a single address", func(t *testing.T) {
			id := generatePeerIds(1)[0]
			addrs := generateAddrs(1)

			ab.AddAddr(id, addrs[0], time.Hour)

			testHas(t, addrs, ab.Addrs(id))
		})

		t.Run("idempotent add single address", func(t *testing.T) {
			id := generatePeerIds(1)[0]
			addrs := generateAddrs(1)

			ab.AddAddr(id, addrs[0], time.Hour)
			ab.AddAddr(id, addrs[0], time.Hour)

			testHas(t, addrs, ab.Addrs(id))
		})

		t.Run("add multiple addresses", func(t *testing.T) {
			id := generatePeerIds(1)[0]
			addrs := generateAddrs(3)

			ab.AddAddrs(id, addrs, time.Hour)
			testHas(t, addrs, ab.Addrs(id))
		})

		t.Run("idempotent add multiple addresses", func(t *testing.T) {
			id := generatePeerIds(1)[0]
			addrs := generateAddrs(3)

			ab.AddAddrs(id, addrs, time.Hour)
			ab.AddAddrs(id, addrs, time.Hour)

			testHas(t, addrs, ab.Addrs(id))
		})
	}
}

func testClearWorks(ab pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		ids := generatePeerIds(2)
		addrs := generateAddrs(5)

		ab.AddAddrs(ids[0], addrs[0:3], time.Hour)
		ab.AddAddrs(ids[1], addrs[3:], time.Hour)

		testHas(t, addrs[0:3], ab.Addrs(ids[0]))
		testHas(t, addrs[3:], ab.Addrs(ids[1]))

		ab.ClearAddrs(ids[0])
		testHas(t, nil, ab.Addrs(ids[0]))
		testHas(t, addrs[3:], ab.Addrs(ids[1]))

		ab.ClearAddrs(ids[1])
		testHas(t, nil, ab.Addrs(ids[0]))
		testHas(t, nil, ab.Addrs(ids[1]))
	}
}

func testSetNegativeTTLClears(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		id := generatePeerIds(1)[0]
		addr := generateAddrs(1)[0]

		m.SetAddr(id, addr, time.Hour)
		testHas(t, []ma.Multiaddr{addr}, m.Addrs(id))

		m.SetAddr(id, addr, -1)
		testHas(t, nil, m.Addrs(id))
	}
}

func testUpdateTTLs(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		t.Run("update ttl of peer with no addrs", func(t *testing.T) {
			id := generatePeerIds(1)[0]

			// Shouldn't panic.
			m.UpdateAddrs(id, time.Hour, time.Minute)
		})

		t.Run("update ttls successfully", func(t *testing.T) {
			ids := generatePeerIds(2)
			addrs1, addrs2 := generateAddrs(2), generateAddrs(2)

			// set two keys with different ttls for each peer.
			m.SetAddr(ids[0], addrs1[0], time.Hour)
			m.SetAddr(ids[0], addrs1[1], time.Minute)
			m.SetAddr(ids[1], addrs2[0], time.Hour)
			m.SetAddr(ids[1], addrs2[1], time.Minute)

			// Sanity check.
			testHas(t, addrs1, m.Addrs(ids[0]))
			testHas(t, addrs2, m.Addrs(ids[1]))

			// Will only affect addrs1[0].
			m.UpdateAddrs(ids[0], time.Hour, time.Second)

			// No immediate effect.
			testHas(t, addrs1, m.Addrs(ids[0]))
			testHas(t, addrs2, m.Addrs(ids[1]))

			// After a wait, addrs[0] is gone.
			time.Sleep(1200 * time.Millisecond)
			testHas(t, addrs1[1:2], m.Addrs(ids[0]))
			testHas(t, addrs2, m.Addrs(ids[1]))

			// Will only affect addrs2[0].
			m.UpdateAddrs(ids[1], time.Hour, time.Second)

			// No immediate effect.
			testHas(t, addrs1[1:2], m.Addrs(ids[0]))
			testHas(t, addrs2, m.Addrs(ids[1]))

			time.Sleep(1200 * time.Millisecond)

			// First addrs is gone in both.
			testHas(t, addrs1[1:], m.Addrs(ids[0]))
			testHas(t, addrs2[1:], m.Addrs(ids[1]))
		})

	}
}

func testNilAddrsDontBreak(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		id := generatePeerIds(1)[0]

		m.SetAddr(id, nil, time.Hour)
		m.AddAddr(id, nil, time.Hour)
	}
}

func testAddressesExpire(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		ids := generatePeerIds(2)
		addrs1 := generateAddrs(3)
		addrs2 := generateAddrs(2)

		m.AddAddrs(ids[0], addrs1, time.Hour)
		m.AddAddrs(ids[1], addrs2, time.Hour)

		testHas(t, addrs1, m.Addrs(ids[0]))
		testHas(t, addrs2, m.Addrs(ids[1]))

		m.AddAddrs(ids[0], addrs1, 2*time.Hour)
		m.AddAddrs(ids[1], addrs2, 2*time.Hour)

		testHas(t, addrs1, m.Addrs(ids[0]))
		testHas(t, addrs2, m.Addrs(ids[1]))

		m.SetAddr(ids[0], addrs1[0], time.Millisecond)
		<-time.After(time.Millisecond * 5)
		testHas(t, addrs1[1:3], m.Addrs(ids[0]))
		testHas(t, addrs2, m.Addrs(ids[1]))

		m.SetAddr(ids[0], addrs1[2], time.Millisecond)
		<-time.After(time.Millisecond * 5)
		testHas(t, addrs1[1:2], m.Addrs(ids[0]))
		testHas(t, addrs2, m.Addrs(ids[1]))

		m.SetAddr(ids[1], addrs2[0], time.Millisecond)
		<-time.After(time.Millisecond * 5)
		testHas(t, addrs1[1:2], m.Addrs(ids[0]))
		testHas(t, addrs2[1:], m.Addrs(ids[1]))

		m.SetAddr(ids[1], addrs2[1], time.Millisecond)
		<-time.After(time.Millisecond * 5)
		testHas(t, addrs1[1:2], m.Addrs(ids[0]))
		testHas(t, nil, m.Addrs(ids[1]))

		m.SetAddr(ids[0], addrs1[1], time.Millisecond)
		<-time.After(time.Millisecond * 5)
		testHas(t, nil, m.Addrs(ids[0]))
		testHas(t, nil, m.Addrs(ids[1]))
	}
}

func testClearWithIterator(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		ids := generatePeerIds(2)
		addrs := generateAddrs(100)

		// Add the peers with 50 addresses each.
		m.AddAddrs(ids[0], addrs[:50], pstore.PermanentAddrTTL)
		m.AddAddrs(ids[1], addrs[50:], pstore.PermanentAddrTTL)

		if all := append(m.Addrs(ids[0]), m.Addrs(ids[1])...); len(all) != 100 {
			t.Fatal("expected pstore to contain both peers with all their maddrs")
		}

		// Since we don't fetch these peers, they won't be present in cache.

		m.ClearAddrs(ids[0])
		if all := append(m.Addrs(ids[0]), m.Addrs(ids[1])...); len(all) != 50 {
			t.Fatal("expected pstore to contain only addrs of peer 2")
		}

		m.ClearAddrs(ids[1])
		if all := append(m.Addrs(ids[0]), m.Addrs(ids[1])...); len(all) != 0 {
			t.Fatal("expected pstore to contain no addresses")
		}
	}
}

func testPeersWithAddrs(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		// cannot run in parallel as the store is modified.
		// go runs sequentially in the specified order
		// see https://blog.golang.org/subtests

		t.Run("empty addrbook", func(t *testing.T) {
			if peers := m.PeersWithAddrs(); len(peers) != 0 {
				t.Fatal("expected to find no peers")
			}
		})

		t.Run("non-empty addrbook", func(t *testing.T) {
			ids := generatePeerIds(2)
			addrs := generateAddrs(10)

			m.AddAddrs(ids[0], addrs[:5], pstore.PermanentAddrTTL)
			m.AddAddrs(ids[1], addrs[5:], pstore.PermanentAddrTTL)

			if peers := m.PeersWithAddrs(); len(peers) != 2 {
				t.Fatal("expected to find 2 peers")
			}
		})
	}
}

func testHas(t *testing.T, exp, act []ma.Multiaddr) {
	t.Helper()
	if len(exp) != len(act) {
		t.Fatalf("lengths not the same. expected %d, got %d\n", len(exp), len(act))
	}

	for _, a := range exp {
		found := false

		for _, b := range act {
			if a.Equal(b) {
				found = true
				break
			}
		}

		if !found {
			t.Fatalf("expected address %s not found", a)
		}
	}
}
<commit_msg>hopefully fix intermittent time-dependent test failure.<commit_after>package test

import (
	"fmt"
	"testing"
	"time"

	peer "github.com/libp2p/go-libp2p-peer"
	pt "github.com/libp2p/go-libp2p-peer/test"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	ma "github.com/multiformats/go-multiaddr"
)

var addressBookSuite = map[string]func(book pstore.AddrBook) func(*testing.T){
	"AddAddress":           testAddAddress,
	"Clear":                testClearWorks,
	"SetNegativeTTLClears": testSetNegativeTTLClears,
	"UpdateTTLs":           testUpdateTTLs,
	"NilAddrsDontBreak":    testNilAddrsDontBreak,
	"AddressesExpire":      testAddressesExpire,
	"ClearWithIter":        testClearWithIterator,
	"PeersWithAddresses":   testPeersWithAddrs,
}

type AddrBookFactory func() (pstore.AddrBook, func())

func TestAddrBook(t *testing.T, factory AddrBookFactory) {
	for name, test := range addressBookSuite {
		// Create a new peerstore.
		ab, closeFunc := factory()

		// Run the test.
		t.Run(name, test(ab))

		// Cleanup.
		if closeFunc != nil {
			closeFunc()
		}
	}
}

func generateAddrs(count int) []ma.Multiaddr {
	var addrs = make([]ma.Multiaddr, count)
	for i := 0; i < count; i++ {
		addrs[i] = multiaddr(fmt.Sprintf("/ip4/1.1.1.%d/tcp/1111", i))
	}
	return addrs
}

func generatePeerIds(count int) []peer.ID {
	var ids = make([]peer.ID, count)
	for i := 0; i < count; i++ {
		ids[i], _ = pt.RandPeerID()
	}
	return ids
}

func testAddAddress(ab pstore.AddrBook) func(*testing.T) {
	return func(t *testing.T) {
		t.Run("add a single address", func(t *testing.T) {
			id := generatePeerIds(1)[0]
			addrs := generateAddrs(1)

			ab.AddAddr(id, addrs[0], time.Hour)

			testHas(t, addrs, ab.Addrs(id))
		})

		t.Run("idempotent add single address", func(t *testing.T) {
			id := generatePeerIds(1)[0]
			addrs := generateAddrs(1)

			ab.AddAddr(id, addrs[0], time.Hour)
			ab.AddAddr(id, addrs[0], time.Hour)

			testHas(t, addrs, ab.Addrs(id))
		})

		t.Run("add multiple addresses", func(t *testing.T) {
			id := generatePeerIds(1)[0]
			addrs := generateAddrs(3)

			ab.AddAddrs(id, addrs, time.Hour)
			testHas(t, addrs, ab.Addrs(id))
		})

		t.Run("idempotent add multiple addresses", func(t *testing.T) {
			id := generatePeerIds(1)[0]
			addrs := generateAddrs(3)

			ab.AddAddrs(id, addrs, time.Hour)
			ab.AddAddrs(id, addrs, time.Hour)

			testHas(t, addrs, ab.Addrs(id))
		})
	}
}

func testClearWorks(ab pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		ids := generatePeerIds(2)
		addrs := generateAddrs(5)

		ab.AddAddrs(ids[0], addrs[0:3], time.Hour)
		ab.AddAddrs(ids[1], addrs[3:], time.Hour)

		testHas(t, addrs[0:3], ab.Addrs(ids[0]))
		testHas(t, addrs[3:], ab.Addrs(ids[1]))

		ab.ClearAddrs(ids[0])
		testHas(t, nil, ab.Addrs(ids[0]))
		testHas(t, addrs[3:], ab.Addrs(ids[1]))

		ab.ClearAddrs(ids[1])
		testHas(t, nil, ab.Addrs(ids[0]))
		testHas(t, nil, ab.Addrs(ids[1]))
	}
}

func testSetNegativeTTLClears(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		id := generatePeerIds(1)[0]
		addr := generateAddrs(1)[0]

		m.SetAddr(id, addr, time.Hour)
		testHas(t, []ma.Multiaddr{addr}, m.Addrs(id))

		m.SetAddr(id, addr, -1)
		testHas(t, nil, m.Addrs(id))
	}
}

func testUpdateTTLs(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		t.Run("update ttl of peer with no addrs", func(t *testing.T) {
			id := generatePeerIds(1)[0]

			// Shouldn't panic.
			m.UpdateAddrs(id, time.Hour, time.Minute)
		})

		t.Run("update ttls successfully", func(t *testing.T) {
			ids := generatePeerIds(2)
			addrs1, addrs2 := generateAddrs(2), generateAddrs(2)

			// set two keys with different ttls for each peer.
			m.SetAddr(ids[0], addrs1[0], time.Hour)
			m.SetAddr(ids[0], addrs1[1], time.Minute)
			m.SetAddr(ids[1], addrs2[0], time.Hour)
			m.SetAddr(ids[1], addrs2[1], time.Minute)

			// Sanity check.
			testHas(t, addrs1, m.Addrs(ids[0]))
			testHas(t, addrs2, m.Addrs(ids[1]))

			// Will only affect addrs1[0].
			m.UpdateAddrs(ids[0], time.Hour, 100*time.Microsecond)

			// No immediate effect.
			testHas(t, addrs1, m.Addrs(ids[0]))
			testHas(t, addrs2, m.Addrs(ids[1]))

			// After a wait, addrs[0] is gone.
			time.Sleep(100 * time.Millisecond)
			testHas(t, addrs1[1:2], m.Addrs(ids[0]))
			testHas(t, addrs2, m.Addrs(ids[1]))

			// Will only affect addrs2[0].
			m.UpdateAddrs(ids[1], time.Hour, 100*time.Microsecond)

			// No immediate effect.
			testHas(t, addrs1[1:2], m.Addrs(ids[0]))
			testHas(t, addrs2, m.Addrs(ids[1]))

			time.Sleep(100 * time.Millisecond)

			// First addrs is gone in both.
			testHas(t, addrs1[1:], m.Addrs(ids[0]))
			testHas(t, addrs2[1:], m.Addrs(ids[1]))
		})

	}
}

func testNilAddrsDontBreak(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		id := generatePeerIds(1)[0]

		m.SetAddr(id, nil, time.Hour)
		m.AddAddr(id, nil, time.Hour)
	}
}

func testAddressesExpire(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		ids := generatePeerIds(2)
		addrs1 := generateAddrs(3)
		addrs2 := generateAddrs(2)

		m.AddAddrs(ids[0], addrs1, time.Hour)
		m.AddAddrs(ids[1], addrs2, time.Hour)

		testHas(t, addrs1, m.Addrs(ids[0]))
		testHas(t, addrs2, m.Addrs(ids[1]))

		m.AddAddrs(ids[0], addrs1, 2*time.Hour)
		m.AddAddrs(ids[1], addrs2, 2*time.Hour)

		testHas(t, addrs1, m.Addrs(ids[0]))
		testHas(t, addrs2, m.Addrs(ids[1]))

		m.SetAddr(ids[0], addrs1[0], time.Millisecond)
		<-time.After(time.Millisecond * 5)
		testHas(t, addrs1[1:3], m.Addrs(ids[0]))
		testHas(t, addrs2, m.Addrs(ids[1]))

		m.SetAddr(ids[0], addrs1[2], time.Millisecond)
		<-time.After(time.Millisecond * 5)
		testHas(t, addrs1[1:2], m.Addrs(ids[0]))
		testHas(t, addrs2, m.Addrs(ids[1]))

		m.SetAddr(ids[1], addrs2[0], time.Millisecond)
		<-time.After(time.Millisecond * 5)
		testHas(t, addrs1[1:2], m.Addrs(ids[0]))
		testHas(t, addrs2[1:], m.Addrs(ids[1]))

		m.SetAddr(ids[1], addrs2[1], time.Millisecond)
		<-time.After(time.Millisecond * 5)
		testHas(t, addrs1[1:2], m.Addrs(ids[0]))
		testHas(t, nil, m.Addrs(ids[1]))

		m.SetAddr(ids[0], addrs1[1], time.Millisecond)
		<-time.After(time.Millisecond * 5)
		testHas(t, nil, m.Addrs(ids[0]))
		testHas(t, nil, m.Addrs(ids[1]))
	}
}

func testClearWithIterator(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		ids := generatePeerIds(2)
		addrs := generateAddrs(100)

		// Add the peers with 50 addresses each.
		m.AddAddrs(ids[0], addrs[:50], pstore.PermanentAddrTTL)
		m.AddAddrs(ids[1], addrs[50:], pstore.PermanentAddrTTL)

		if all := append(m.Addrs(ids[0]), m.Addrs(ids[1])...); len(all) != 100 {
			t.Fatal("expected pstore to contain both peers with all their maddrs")
		}

		// Since we don't fetch these peers, they won't be present in cache.

		m.ClearAddrs(ids[0])
		if all := append(m.Addrs(ids[0]), m.Addrs(ids[1])...); len(all) != 50 {
			t.Fatal("expected pstore to contain only addrs of peer 2")
		}

		m.ClearAddrs(ids[1])
		if all := append(m.Addrs(ids[0]), m.Addrs(ids[1])...); len(all) != 0 {
			t.Fatal("expected pstore to contain no addresses")
		}
	}
}

func testPeersWithAddrs(m pstore.AddrBook) func(t *testing.T) {
	return func(t *testing.T) {
		// cannot run in parallel as the store is modified.
		// go runs sequentially in the specified order
		// see https://blog.golang.org/subtests

		t.Run("empty addrbook", func(t *testing.T) {
			if peers := m.PeersWithAddrs(); len(peers) != 0 {
				t.Fatal("expected to find no peers")
			}
		})

		t.Run("non-empty addrbook", func(t *testing.T) {
			ids := generatePeerIds(2)
			addrs := generateAddrs(10)

			m.AddAddrs(ids[0], addrs[:5], pstore.PermanentAddrTTL)
			m.AddAddrs(ids[1], addrs[5:], pstore.PermanentAddrTTL)

			if peers := m.PeersWithAddrs(); len(peers) != 2 {
				t.Fatal("expected to find 2 peers")
			}
		})
	}
}

func testHas(t *testing.T, exp, act []ma.Multiaddr) {
	t.Helper()
	if len(exp) != len(act) {
		t.Fatalf("lengths not the same. expected %d, got %d\n", len(exp), len(act))
	}

	for _, a := range exp {
		found := false

		for _, b := range act {
			if a.Equal(b) {
				found = true
				break
			}
		}

		if !found {
			t.Fatalf("expected address %s not found", a)
		}
	}
}
<|endoftext|>