<commit_before>package peer

import (
	"doozer/store"
	"github.com/ha/doozer"
	"testing"
)

func Benchmark1DoozerClientSet(b *testing.B) {
	b.StopTimer()
	l := mustListen()
	defer l.Close()
	a := l.Addr().String()
	u := mustListenPacket(a)
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9)

	cl := dial(l.Addr().String())

	b.StartTimer()
	for i := 0; i < b.N; i++ {
		cl.Set("/test", store.Clobber, nil)
	}
}

func Benchmark1DoozerConClientSet(b *testing.B) {
	b.StopTimer()
	l := mustListen()
	defer l.Close()
	a := l.Addr().String()
	u := mustListenPacket(a)
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9)

	cl := dial(l.Addr().String())

	c := make(chan bool, b.N)
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		go func() {
			cl.Set("/test", store.Clobber, nil)
			c <- true
		}()
	}
	for i := 0; i < b.N; i++ {
		<-c
	}
}

func Benchmark5DoozerClientSet(b *testing.B) {
	b.StopTimer()
	l := mustListen()
	defer l.Close()
	a := l.Addr().String()
	u := mustListenPacket(a)
	defer u.Close()

	l1 := mustListen()
	defer l1.Close()
	u1 := mustListenPacket(l1.Addr().String())
	defer u1.Close()
	l2 := mustListen()
	defer l2.Close()
	u2 := mustListenPacket(l2.Addr().String())
	defer u2.Close()
	l3 := mustListen()
	defer l3.Close()
	u3 := mustListenPacket(l3.Addr().String())
	defer u3.Close()
	l4 := mustListen()
	defer l4.Close()
	u4 := mustListenPacket(l4.Addr().String())
	defer u4.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 1e8, 3e9)
	go Main("a", "Y", "", "", "", dial(a), u1, l1, nil, 1e9, 1e8, 3e9)
	go Main("a", "Z", "", "", "", dial(a), u2, l2, nil, 1e9, 1e8, 3e9)
	go Main("a", "V", "", "", "", dial(a), u3, l3, nil, 1e9, 1e8, 3e9)
	go Main("a", "W", "", "", "", dial(a), u4, l4, nil, 1e9, 1e8, 3e9)

	cl := dial(l.Addr().String())
	cl.Set("/ctl/cal/1", store.Missing, nil)
	cl.Set("/ctl/cal/2", store.Missing, nil)
	cl.Set("/ctl/cal/3", store.Missing, nil)
	cl.Set("/ctl/cal/4", store.Missing, nil)

	b.StartTimer()
	for i := 0; i < b.N; i++ {
		cl.Set("/test", store.Clobber, nil)
	}
}

func Benchmark5DoozerConClientSet(b *testing.B) {
	b.StopTimer()
	l := mustListen()
	defer l.Close()
	a := l.Addr().String()
	u := mustListenPacket(a)
	defer u.Close()

	l1 := mustListen()
	defer l1.Close()
	u1 := mustListenPacket(l1.Addr().String())
	defer u1.Close()
	l2 := mustListen()
	defer l2.Close()
	u2 := mustListenPacket(l2.Addr().String())
	defer u2.Close()
	l3 := mustListen()
	defer l3.Close()
	u3 := mustListenPacket(l3.Addr().String())
	defer u3.Close()
	l4 := mustListen()
	defer l4.Close()
	u4 := mustListenPacket(l4.Addr().String())
	defer u4.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 1e8, 3e9)
	go Main("a", "Y", "", "", "", dial(a), u1, l1, nil, 1e9, 1e8, 3e9)
	go Main("a", "Z", "", "", "", dial(a), u2, l2, nil, 1e9, 1e8, 3e9)
	go Main("a", "V", "", "", "", dial(a), u3, l3, nil, 1e9, 1e8, 3e9)
	go Main("a", "W", "", "", "", dial(a), u4, l4, nil, 1e9, 1e8, 3e9)

	cl := dial(l.Addr().String())
	cl.Set("/ctl/cal/1", store.Missing, nil)
	cl.Set("/ctl/cal/2", store.Missing, nil)
	cl.Set("/ctl/cal/3", store.Missing, nil)
	cl.Set("/ctl/cal/4", store.Missing, nil)

	cls := []*doozer.Conn{
		cl,
		dial(l1.Addr().String()),
		dial(l2.Addr().String()),
		dial(l3.Addr().String()),
		dial(l4.Addr().String()),
	}

	c := make(chan bool, b.N)
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		i := i
		go func() {
			cls[i%len(cls)].Set("/test", store.Clobber, nil)
			c <- true
		}()
	}
	for i := 0; i < b.N; i++ {
		<-c
	}
}

func dial(addr string) *doozer.Conn {
	c, err := doozer.Dial(addr)
	if err != nil {
		panic(err)
	}
	return c
}
<commit_msg>test with fixed concurrency and report latency<commit_after>package peer

import (
	"doozer/store"
	"github.com/ha/doozer"
	"testing"
	"time"
)

func Benchmark1DoozerClientSet(b *testing.B) {
	b.StopTimer()
	l := mustListen()
	defer l.Close()
	a := l.Addr().String()
	u := mustListenPacket(a)
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9)

	cl := dial(l.Addr().String())

	b.StartTimer()
	for i := 0; i < b.N; i++ {
		cl.Set("/test", store.Clobber, nil)
	}
}

func Benchmark1DoozerConClientSet(b *testing.B) {
	b.StopTimer()
	l := mustListen()
	defer l.Close()
	a := l.Addr().String()
	u := mustListenPacket(a)
	defer u.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 2e9, 3e9)

	cl := dial(l.Addr().String())

	c := make(chan bool, b.N)
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		go func() {
			cl.Set("/test", store.Clobber, nil)
			c <- true
		}()
	}
	for i := 0; i < b.N; i++ {
		<-c
	}
}

func Benchmark5DoozerClientSet(b *testing.B) {
	b.StopTimer()
	l := mustListen()
	defer l.Close()
	a := l.Addr().String()
	u := mustListenPacket(a)
	defer u.Close()

	l1 := mustListen()
	defer l1.Close()
	u1 := mustListenPacket(l1.Addr().String())
	defer u1.Close()
	l2 := mustListen()
	defer l2.Close()
	u2 := mustListenPacket(l2.Addr().String())
	defer u2.Close()
	l3 := mustListen()
	defer l3.Close()
	u3 := mustListenPacket(l3.Addr().String())
	defer u3.Close()
	l4 := mustListen()
	defer l4.Close()
	u4 := mustListenPacket(l4.Addr().String())
	defer u4.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 1e8, 3e9)
	go Main("a", "Y", "", "", "", dial(a), u1, l1, nil, 1e9, 1e8, 3e9)
	go Main("a", "Z", "", "", "", dial(a), u2, l2, nil, 1e9, 1e8, 3e9)
	go Main("a", "V", "", "", "", dial(a), u3, l3, nil, 1e9, 1e8, 3e9)
	go Main("a", "W", "", "", "", dial(a), u4, l4, nil, 1e9, 1e8, 3e9)

	cl := dial(l.Addr().String())
	cl.Set("/ctl/cal/1", store.Missing, nil)
	cl.Set("/ctl/cal/2", store.Missing, nil)
	cl.Set("/ctl/cal/3", store.Missing, nil)
	cl.Set("/ctl/cal/4", store.Missing, nil)

	b.StartTimer()
	for i := 0; i < b.N; i++ {
		cl.Set("/test", store.Clobber, nil)
	}
}

func Benchmark5DoozerConClientSet(b *testing.B) {
	b.StopTimer()
	l := mustListen()
	defer l.Close()
	a := l.Addr().String()
	u := mustListenPacket(a)
	defer u.Close()

	l1 := mustListen()
	defer l1.Close()
	u1 := mustListenPacket(l1.Addr().String())
	defer u1.Close()
	l2 := mustListen()
	defer l2.Close()
	u2 := mustListenPacket(l2.Addr().String())
	defer u2.Close()
	l3 := mustListen()
	defer l3.Close()
	u3 := mustListenPacket(l3.Addr().String())
	defer u3.Close()
	l4 := mustListen()
	defer l4.Close()
	u4 := mustListenPacket(l4.Addr().String())
	defer u4.Close()

	go Main("a", "X", "", "", "", nil, u, l, nil, 1e9, 1e8, 3e9)
	go Main("a", "Y", "", "", "", dial(a), u1, l1, nil, 1e9, 1e8, 3e9)
	go Main("a", "Z", "", "", "", dial(a), u2, l2, nil, 1e9, 1e8, 3e9)
	go Main("a", "V", "", "", "", dial(a), u3, l3, nil, 1e9, 1e8, 3e9)
	go Main("a", "W", "", "", "", dial(a), u4, l4, nil, 1e9, 1e8, 3e9)

	cl := dial(l.Addr().String())
	cl.Set("/ctl/cal/1", store.Missing, nil)
	cl.Set("/ctl/cal/2", store.Missing, nil)
	cl.Set("/ctl/cal/3", store.Missing, nil)
	cl.Set("/ctl/cal/4", store.Missing, nil)

	cls := []*doozer.Conn{
		cl,
		dial(l1.Addr().String()),
		dial(l2.Addr().String()),
		dial(l3.Addr().String()),
		dial(l4.Addr().String()),
	}

	const con = 2000
	c := make(chan int, b.N)
	times := make([]int64, b.N)
	done := make(chan bool, con)
	f := func() {
		for i := range c {
			a := time.Nanoseconds()
			cls[i%len(cls)].Set("/test", store.Clobber, nil)
			b := time.Nanoseconds()
			times[i] = b - a
		}
		done <- true
	}
	for i := 0; i < con; i++ {
		go f()
	}

	println("---")
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		c <- i
	}
	close(c)
	for i := 0; i < con; i++ {
		<-done
	}
	b.StopTimer()
	for _, t := range times {
		println("el", t)
	}
}

func dial(addr string) *doozer.Conn {
	c, err := doozer.Dial(addr)
	if err != nil {
		panic(err)
	}
	return c
}
<|endoftext|>


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

package runtime_test

import (
	"runtime"
	"testing"
)

func TestGcSys(t *testing.T) {
	if runtime.GOARCH != "amd64" {
		// TODO(adg): remove this when precise gc is implemented
		t.Logf("skipping on non-amd64 systems")
		return
	}
	memstats := new(runtime.MemStats)
	runtime.GC()
	runtime.ReadMemStats(memstats)
	sys := memstats.Sys

	runtime.MemProfileRate = 0 // disable profiler

	itercount := 1000000
	if testing.Short() {
		itercount = 100000
	}
	for i := 0; i < itercount; i++ {
		workthegc()
	}

	// Should only be using a few MB.
	runtime.ReadMemStats(memstats)
	if sys > memstats.Sys {
		sys = 0
	} else {
		sys = memstats.Sys - sys
	}
	t.Logf("used %d extra bytes", sys)
	if sys > 4<<20 {
		t.Fatalf("using too much memory: %d bytes", sys)
	}
}

func workthegc() []byte {
	return make([]byte, 1029)
}
<commit_msg>runtime: relax TestGcSys<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime_test

import (
	"runtime"
	"testing"
)

func TestGcSys(t *testing.T) {
	memstats := new(runtime.MemStats)
	runtime.GC()
	runtime.ReadMemStats(memstats)
	sys := memstats.Sys

	runtime.MemProfileRate = 0 // disable profiler

	itercount := 1000000
	if testing.Short() {
		itercount = 100000
	}
	for i := 0; i < itercount; i++ {
		workthegc()
	}

	// Should only be using a few MB.
	// We allocated 100 MB or (if not short) 1 GB.
	runtime.ReadMemStats(memstats)
	if sys > memstats.Sys {
		sys = 0
	} else {
		sys = memstats.Sys - sys
	}
	t.Logf("used %d extra bytes", sys)
	if sys > 16<<20 {
		t.Fatalf("using too much memory: %d bytes", sys)
	}
}

func workthegc() []byte {
	return make([]byte, 1029)
}
<|endoftext|>


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

package testing

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
	"time"
)

type InternalExample struct {
	Name   string
	F      func()
	Output string
}

func RunExamples(examples []InternalExample) (ok bool) {
	ok = true

	var eg InternalExample

	stdout, stderr := os.Stdout, os.Stderr
	defer func() {
		os.Stdout, os.Stderr = stdout, stderr
		if e := recover(); e != nil {
			fmt.Printf("--- FAIL: %s\npanic: %v\n", eg.Name, e)
			os.Exit(1)
		}
	}()

	for _, eg = range examples {
		if *chatty {
			fmt.Printf("=== RUN: %s\n", eg.Name)
		}

		// capture stdout and stderr
		r, w, err := os.Pipe()
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
		os.Stdout, os.Stderr = w, w
		outC := make(chan string)
		go func() {
			buf := new(bytes.Buffer)
			_, err := io.Copy(buf, r)
			if err != nil {
				fmt.Fprintf(stderr, "testing: copying pipe: %v\n", err)
				os.Exit(1)
			}
			outC <- buf.String()
		}()

		// run example
		t0 := time.Now()
		eg.F()
		dt := time.Now().Sub(t0)

		// close pipe, restore stdout/stderr, get output
		w.Close()
		os.Stdout, os.Stderr = stdout, stderr
		out := <-outC

		// report any errors
		tstr := fmt.Sprintf("(%.2f seconds)", dt.Seconds())
		if g, e := strings.TrimSpace(out), strings.TrimSpace(eg.Output); g != e {
			fmt.Printf("--- FAIL: %s %s\ngot:\n%s\nwant:\n%s\n",
				eg.Name, tstr, g, e)
			ok = false
		} else if *chatty {
			fmt.Printf("--- PASS: %s %s\n", eg.Name, tstr)
		}
	}

	return
}
<commit_msg>testing: do not recover example's panic         So as to give out stack trace for panic in examples.         This behavior also matches the tests'.         Fixes #2691.<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package testing

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
	"time"
)

type InternalExample struct {
	Name   string
	F      func()
	Output string
}

func RunExamples(examples []InternalExample) (ok bool) {
	ok = true

	var eg InternalExample

	stdout, stderr := os.Stdout, os.Stderr

	for _, eg = range examples {
		if *chatty {
			fmt.Printf("=== RUN: %s\n", eg.Name)
		}

		// capture stdout and stderr
		r, w, err := os.Pipe()
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
		os.Stdout, os.Stderr = w, w
		outC := make(chan string)
		go func() {
			buf := new(bytes.Buffer)
			_, err := io.Copy(buf, r)
			if err != nil {
				fmt.Fprintf(stderr, "testing: copying pipe: %v\n", err)
				os.Exit(1)
			}
			outC <- buf.String()
		}()

		// run example
		t0 := time.Now()
		eg.F()
		dt := time.Now().Sub(t0)

		// close pipe, restore stdout/stderr, get output
		w.Close()
		os.Stdout, os.Stderr = stdout, stderr
		out := <-outC

		// report any errors
		tstr := fmt.Sprintf("(%.2f seconds)", dt.Seconds())
		if g, e := strings.TrimSpace(out), strings.TrimSpace(eg.Output); g != e {
			fmt.Printf("--- FAIL: %s %s\ngot:\n%s\nwant:\n%s\n",
				eg.Name, tstr, g, e)
			ok = false
		} else if *chatty {
			fmt.Printf("--- PASS: %s %s\n", eg.Name, tstr)
		}
	}

	return
}
<|endoftext|>


<commit_before>package class

import (
	"fmt"
	. "jvmgo/any"
	"jvmgo/classfile"
	"jvmgo/classpath"
	"jvmgo/jvm/jerrors"
	"jvmgo/jvm/options"
)

const (
	jlObjectClassName       = "java/lang/Object"
	jlClassClassName        = "java/lang/Class"
	jlCloneableClassName    = "java/lang/Cloneable"
	ioSerializableClassName = "java/io/Serializable"
	jlThreadClassName       = "java/lang/Thread"
	jlStringClassName       = "java/lang/String"
)

var (
	bootLoader           *ClassLoader // bootstrap class loader
	_jlObjectClass       *Class
	_jlClassClass        *Class
	_jlCloneableClass    *Class
	_ioSerializableClass *Class
	_jlThreadClass       *Class
	_jlStringClass       *Class
)

/*
class names:
    - primitive types: boolean, byte, int ...
    - primitive arrays: [Z, [B, [I ...
    - non-array classes: java/lang/Object ...
    - array classes: [Ljava/lang/Object; ...
*/

// the bootstrap class loader
type ClassLoader struct {
	classPath *classpath.ClassPath
	classMap  map[string]*Class
}

func BootLoader() *ClassLoader {
	return bootLoader
}

func InitBootLoader(cp *classpath.ClassPath) {
	bootLoader = &ClassLoader{
		classPath: cp,
		classMap:  map[string]*Class{},
	}
	bootLoader._init()
}

func (self *ClassLoader) ClassPath() *classpath.ClassPath {
	return self.classPath
}

func (self *ClassLoader) _init() {
	_jlObjectClass = self.LoadClass(jlObjectClassName)
	_jlClassClass = self.LoadClass(jlClassClassName)
	for _, class := range self.classMap {
		if class.jClass == nil {
			class.jClass = _jlClassClass.NewObj()
			class.jClass.extra = class
		}
	}
	_jlCloneableClass = self.LoadClass(jlCloneableClassName)
	_ioSerializableClass = self.LoadClass(ioSerializableClassName)
	_jlThreadClass = self.LoadClass(jlThreadClassName)
	_jlStringClass = self.LoadClass(jlStringClassName)
	self.loadPrimitiveClasses()
	self.loadPrimitiveArrayClasses()
}

func (self *ClassLoader) loadPrimitiveClasses() {
	for primitiveType, _ := range primitiveTypes {
		self.loadPrimitiveClass(primitiveType)
	}
}
func (self *ClassLoader) loadPrimitiveClass(className string) {
	class := &Class{name: className}
	//class.classLoader = self
	class.jClass = _jlClassClass.NewObj()
	class.jClass.extra = class
	class.MarkInitialized()
	self.classMap[className] = class
}

func (self *ClassLoader) loadPrimitiveArrayClasses() {
	for _, descriptor := range primitiveTypes {
		self.loadArrayClass("[" + descriptor)
	}
}
func (self *ClassLoader) loadArrayClass(className string) *Class {
	class := &Class{name: className}
	//class.classLoader = self
	class.superClass = _jlObjectClass
	class.interfaces = []*Class{_jlCloneableClass, _ioSerializableClass}
	class.jClass = _jlClassClass.NewObj()
	class.jClass.extra = class
	createVtable(class)
	class.MarkInitialized()
	self.classMap[className] = class
	return class
}

func (self *ClassLoader) getRefArrayClass(componentClass *Class) *Class {
	arrClassName := "[L" + componentClass.Name() + ";"
	return self._getRefArrayClass(arrClassName)
}
func (self *ClassLoader) _getRefArrayClass(arrClassName string) *Class {
	if arrClass, ok := self.classMap[arrClassName]; ok {
		return arrClass
	}
	return self.loadArrayClass(arrClassName)
}

// todo
func (self *ClassLoader) GetPrimitiveClass(name string) *Class {
	return self.getClass(name)
}

func (self *ClassLoader) JLObjectClass() *Class {
	return _jlObjectClass
}
func (self *ClassLoader) JLClassClass() *Class {
	return _jlClassClass
}
func (self *ClassLoader) JLStringClass() *Class {
	return _jlStringClass
}
func (self *ClassLoader) JLThreadClass() *Class {
	return _jlThreadClass
}

// todo dangerous
func (self *ClassLoader) getClass(name string) *Class {
	if class, ok := self.classMap[name]; ok {
		return class
	}
	panic("class not loaded! " + name)
}

func (self *ClassLoader) LoadClass(name string) *Class {
	if class, ok := self.classMap[name]; ok {
		// already loaded
		return class
	} else if name[0] == '[' {
		// array class
		return self._getRefArrayClass(name)
	} else {
		return self.reallyLoadClass(name)
	}
}

func (self *ClassLoader) reallyLoadClass(name string) *Class {
	cpEntry, data := self.readClassData(name)
	class := self._loadClass(name, data)
	class.loadedFrom = cpEntry

	if options.VerboseClass {
		fmt.Printf("[Loaded %s from %s]\n", name, cpEntry)
	}

	return class
}

func (self *ClassLoader) readClassData(name string) (classpath.ClassPathEntry, []byte) {
	cpEntry, classData, err := self.classPath.ReadClassData(name)
	if err != nil {
		panic(jerrors.NewClassNotFoundError(SlashToDot(name)))
	}

	return cpEntry, classData
}

func (self *ClassLoader) parseClassData(name string, data []byte) *Class {
	cf, err := classfile.ParseClassFile(data)
	if err != nil {
		// todo
		panic("failed to parse class file: " + name + "!" + err.Error())
	}

	return newClass(cf)
}

func (self *ClassLoader) _loadClass(name string, data []byte) *Class {
	class := self.parseClassData(name, data)
	hackClass(class)
	self.resolveSuperClass(class)
	self.resolveInterfaces(class)
	calcStaticFieldSlots(class)
	calcInstanceFieldSlots(class)
	createVtable(class)
	prepare(class)
	// todo
	//class.classLoader = self
	self.classMap[name] = class

	if _jlClassClass != nil {
		class.jClass = _jlClassClass.NewObj()
		class.jClass.extra = class
	}

	return class
}

// todo
func hackClass(class *Class) {
	if class.name == "java/lang/ClassLoader" {
		loadLibrary := class.GetStaticMethod("loadLibrary", "(Ljava/lang/Class;Ljava/lang/String;Z)V")
		loadLibrary.code = []byte{0xb1} // return void
	}
}

// todo
func (self *ClassLoader) resolveSuperClass(class *Class) {
	if class.superClassName != "" {
		class.superClass = self.LoadClass(class.superClassName)
	}
}
func (self *ClassLoader) resolveInterfaces(class *Class) {
	interfaceCount := len(class.interfaceNames)
	if interfaceCount > 0 {
		class.interfaces = make([]*Class, interfaceCount)
		for i, interfaceName := range class.interfaceNames {
			class.interfaces[i] = self.LoadClass(interfaceName)
		}
	}
}

func calcStaticFieldSlots(class *Class) {
	slotId := uint(0)
	for _, field := range class.fields {
		if field.IsStatic() {
			field.slot = slotId
			slotId++
		}
	}
	class.staticFieldCount = slotId
}

func calcInstanceFieldSlots(class *Class) {
	slotId := uint(0)
	if class.superClassName != "" {
		slotId = class.superClass.instanceFieldCount
	}
	for _, field := range class.fields {
		if !field.IsStatic() {
			field.slot = slotId
			slotId++
		}
	}
	class.instanceFieldCount = slotId
}

func prepare(class *Class) {
	class.staticFieldValues = make([]Any, class.staticFieldCount)
	for _, field := range class.fields {
		if field.IsStatic() {
			class.staticFieldValues[field.slot] = field.defaultValue()
		}
	}
}

// todo
func (self *ClassLoader) DefineClass(name string, data []byte) *Class {
	return self._loadClass(name, data)
}
<commit_msg>code refactor<commit_after>package class

import (
	"fmt"
	. "jvmgo/any"
	"jvmgo/classfile"
	"jvmgo/classpath"
	"jvmgo/jvm/jerrors"
	"jvmgo/jvm/options"
)

const (
	jlObjectClassName       = "java/lang/Object"
	jlClassClassName        = "java/lang/Class"
	jlStringClassName       = "java/lang/String"
	jlThreadClassName       = "java/lang/Thread"
	jlCloneableClassName    = "java/lang/Cloneable"
	ioSerializableClassName = "java/io/Serializable"
)

var (
	bootLoader           *ClassLoader // bootstrap class loader
	_jlObjectClass       *Class
	_jlClassClass        *Class
	_jlStringClass       *Class
	_jlThreadClass       *Class
	_jlCloneableClass    *Class
	_ioSerializableClass *Class
)

/*
class names:
    - primitive types: boolean, byte, int ...
    - primitive arrays: [Z, [B, [I ...
    - non-array classes: java/lang/Object ...
    - array classes: [Ljava/lang/Object; ...
*/

// the bootstrap class loader
type ClassLoader struct {
	classPath *classpath.ClassPath
	classMap  map[string]*Class
}

func BootLoader() *ClassLoader {
	return bootLoader
}

func InitBootLoader(cp *classpath.ClassPath) {
	bootLoader = &ClassLoader{
		classPath: cp,
		classMap:  map[string]*Class{},
	}
	bootLoader._init()
}

func (self *ClassLoader) _init() {
	_jlObjectClass = self.LoadClass(jlObjectClassName)
	_jlClassClass = self.LoadClass(jlClassClassName)
	for _, class := range self.classMap {
		if class.jClass == nil {
			class.jClass = _jlClassClass.NewObj()
			class.jClass.extra = class
		}
	}
	_jlCloneableClass = self.LoadClass(jlCloneableClassName)
	_ioSerializableClass = self.LoadClass(ioSerializableClassName)
	_jlThreadClass = self.LoadClass(jlThreadClassName)
	_jlStringClass = self.LoadClass(jlStringClassName)
	self.loadPrimitiveClasses()
	self.loadPrimitiveArrayClasses()
}

func (self *ClassLoader) loadPrimitiveClasses() {
	for primitiveType, _ := range primitiveTypes {
		self.loadPrimitiveClass(primitiveType)
	}
}
func (self *ClassLoader) loadPrimitiveClass(className string) {
	class := &Class{name: className}
	//class.classLoader = self
	class.jClass = _jlClassClass.NewObj()
	class.jClass.extra = class
	class.MarkInitialized()
	self.classMap[className] = class
}

func (self *ClassLoader) loadPrimitiveArrayClasses() {
	for _, descriptor := range primitiveTypes {
		self.loadArrayClass("[" + descriptor)
	}
}
func (self *ClassLoader) loadArrayClass(className string) *Class {
	class := &Class{name: className}
	//class.classLoader = self
	class.superClass = _jlObjectClass
	class.interfaces = []*Class{_jlCloneableClass, _ioSerializableClass}
	class.jClass = _jlClassClass.NewObj()
	class.jClass.extra = class
	createVtable(class)
	class.MarkInitialized()
	self.classMap[className] = class
	return class
}

func (self *ClassLoader) getRefArrayClass(componentClass *Class) *Class {
	arrClassName := "[L" + componentClass.Name() + ";"
	return self._getRefArrayClass(arrClassName)
}
func (self *ClassLoader) _getRefArrayClass(arrClassName string) *Class {
	if arrClass, ok := self.classMap[arrClassName]; ok {
		return arrClass
	}
	return self.loadArrayClass(arrClassName)
}

func (self *ClassLoader) ClassPath() *classpath.ClassPath {
	return self.classPath
}

func (self *ClassLoader) JLObjectClass() *Class {
	return _jlObjectClass
}
func (self *ClassLoader) JLClassClass() *Class {
	return _jlClassClass
}
func (self *ClassLoader) JLStringClass() *Class {
	return _jlStringClass
}
func (self *ClassLoader) JLThreadClass() *Class {
	return _jlThreadClass
}

// todo
func (self *ClassLoader) GetPrimitiveClass(name string) *Class {
	return self.getClass(name)
}

// todo dangerous
func (self *ClassLoader) getClass(name string) *Class {
	if class, ok := self.classMap[name]; ok {
		return class
	}
	panic("class not loaded! " + name)
}

func (self *ClassLoader) LoadClass(name string) *Class {
	if class, ok := self.classMap[name]; ok {
		// already loaded
		return class
	} else if name[0] == '[' {
		// array class
		return self._getRefArrayClass(name)
	} else {
		return self.reallyLoadClass(name)
	}
}

func (self *ClassLoader) reallyLoadClass(name string) *Class {
	cpEntry, data := self.readClassData(name)
	class := self._loadClass(name, data)
	class.loadedFrom = cpEntry

	if options.VerboseClass {
		fmt.Printf("[Loaded %s from %s]\n", name, cpEntry)
	}

	return class
}

func (self *ClassLoader) readClassData(name string) (classpath.ClassPathEntry, []byte) {
	cpEntry, classData, err := self.classPath.ReadClassData(name)
	if err != nil {
		panic(jerrors.NewClassNotFoundError(SlashToDot(name)))
	}

	return cpEntry, classData
}

func (self *ClassLoader) parseClassData(name string, data []byte) *Class {
	cf, err := classfile.ParseClassFile(data)
	if err != nil {
		// todo
		panic("failed to parse class file: " + name + "!" + err.Error())
	}

	return newClass(cf)
}

func (self *ClassLoader) _loadClass(name string, data []byte) *Class {
	class := self.parseClassData(name, data)
	hackClass(class)
	self.resolveSuperClass(class)
	self.resolveInterfaces(class)
	calcStaticFieldSlots(class)
	calcInstanceFieldSlots(class)
	createVtable(class)
	prepare(class)
	// todo
	//class.classLoader = self
	self.classMap[name] = class

	if _jlClassClass != nil {
		class.jClass = _jlClassClass.NewObj()
		class.jClass.extra = class
	}

	return class
}

// todo
func hackClass(class *Class) {
	if class.name == "java/lang/ClassLoader" {
		loadLibrary := class.GetStaticMethod("loadLibrary", "(Ljava/lang/Class;Ljava/lang/String;Z)V")
		loadLibrary.code = []byte{0xb1} // return void
	}
}

// todo
func (self *ClassLoader) resolveSuperClass(class *Class) {
	if class.superClassName != "" {
		class.superClass = self.LoadClass(class.superClassName)
	}
}
func (self *ClassLoader) resolveInterfaces(class *Class) {
	interfaceCount := len(class.interfaceNames)
	if interfaceCount > 0 {
		class.interfaces = make([]*Class, interfaceCount)
		for i, interfaceName := range class.interfaceNames {
			class.interfaces[i] = self.LoadClass(interfaceName)
		}
	}
}

func calcStaticFieldSlots(class *Class) {
	slotId := uint(0)
	for _, field := range class.fields {
		if field.IsStatic() {
			field.slot = slotId
			slotId++
		}
	}
	class.staticFieldCount = slotId
}

func calcInstanceFieldSlots(class *Class) {
	slotId := uint(0)
	if class.superClassName != "" {
		slotId = class.superClass.instanceFieldCount
	}
	for _, field := range class.fields {
		if !field.IsStatic() {
			field.slot = slotId
			slotId++
		}
	}
	class.instanceFieldCount = slotId
}

func prepare(class *Class) {
	class.staticFieldValues = make([]Any, class.staticFieldCount)
	for _, field := range class.fields {
		if field.IsStatic() {
			class.staticFieldValues[field.slot] = field.defaultValue()
		}
	}
}

// todo
func (self *ClassLoader) DefineClass(name string, data []byte) *Class {
	return self._loadClass(name, data)
}
<|endoftext|>


<commit_before>// Copyright 2022 Gravitational, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package webauthncli

import (
	"context"
	"errors"
	"strings"

	"github.com/gravitational/teleport/api/client/proto"
	"github.com/gravitational/teleport/lib/auth/touchid"
	"github.com/gravitational/trace"

	wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
	log "github.com/sirupsen/logrus"
)

// AuthenticatorAttachment allows callers to choose a specific attachment.
type AuthenticatorAttachment int

const (
	AttachmentAuto AuthenticatorAttachment = iota
	AttachmentCrossPlatform
	AttachmentPlatform
)

// LoginOpts groups non-mandatory options for Login.
type LoginOpts struct {
	// User is the desired credential username for login.
	// If empty, Login may either choose a credential or error due to ambiguity.
	User string
	// OptimisticAssertion allows Login to skip credential listing and attempt
	// to assert directly. The drawback of an optimistic assertion is that the
	// authenticator chooses the login credential, so Login can't guarantee that
	// the User field will be respected. The upside is that it saves a touch for
	// some devices.
	// Login may decide to forego optimistic assertions if it wouldn't save a
	// touch.
	OptimisticAssertion bool
	// AuthenticatorAttachment specifies the desired authenticator attachment.
	AuthenticatorAttachment AuthenticatorAttachment
}

// Login performs client-side, U2F-compatible, Webauthn login.
// This method blocks until either device authentication is successful or the
// context is cancelled. Calling Login without a deadline or cancel condition
// may cause it to block forever.
// The informed user is used to disambiguate credentials in case of passwordless
// logins.
// It returns an MFAAuthenticateResponse and the credential user, if a resident
// credential is used.
// The caller is expected to react to LoginPrompt in order to prompt the user at
// appropriate times. Login may choose different flows depending on the type of
// authentication and connected devices.
func Login(
	ctx context.Context,
	origin string, assertion *wanlib.CredentialAssertion, prompt LoginPrompt, opts *LoginOpts,
) (*proto.MFAAuthenticateResponse, string, error) {
	// origin vs RPID sanity check.
	// Doesn't necessarily means a failure, but it's likely to be one.
	switch rpID := assertion.Response.RelyingPartyID; {
	case origin == "", assertion == nil: // let downstream handle empty/nil
	case !strings.HasPrefix(origin, "https://"+rpID):
		log.Warnf(""+
			"WebAuthn: origin and RPID mismatch, "+
			"if you are having authentication problems double check your proxy address "+
			"(%q vs %q)", origin, rpID)
	}

	var attachment AuthenticatorAttachment
	var user string
	if opts != nil {
		attachment = opts.AuthenticatorAttachment
		user = opts.User
	}

	switch attachment {
	case AttachmentCrossPlatform:
		log.Debug("Cross-platform login")
		return crossPlatformLogin(ctx, origin, assertion, prompt, opts)
	case AttachmentPlatform:
		log.Debug("Platform login")
		return platformLogin(origin, user, assertion)
	default:
		log.Debug("Attempting platform login")
		resp, credentialUser, err := platformLogin(origin, user, assertion)
		if !errors.Is(err, &touchid.ErrAttemptFailed{}) {
			return resp, credentialUser, trace.Wrap(err)
		}

		log.WithError(err).Debug("Platform login failed, falling back to cross-platform")
		return crossPlatformLogin(ctx, origin, assertion, prompt, opts)
	}
}

func crossPlatformLogin(
	ctx context.Context,
	origin string, assertion *wanlib.CredentialAssertion, prompt LoginPrompt, opts *LoginOpts,
) (*proto.MFAAuthenticateResponse, string, error) {
	if IsFIDO2Available() {
		log.Debug("FIDO2: Using libfido2 for assertion")
		return FIDO2Login(ctx, origin, assertion, prompt, opts)
	}

	prompt.PromptTouch()
	resp, err := U2FLogin(ctx, origin, assertion)
	return resp, "" /* credentialUser */, err
}

func platformLogin(origin, user string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, string, error) {
	resp, credentialUser, err := touchid.AttemptLogin(origin, user, assertion)
	if err != nil {
		return nil, "", err
	}
	return &proto.MFAAuthenticateResponse{
		Response: &proto.MFAAuthenticateResponse_Webauthn{
			Webauthn: wanlib.CredentialAssertionResponseToProto(resp),
		},
	}, credentialUser, nil
}

// Register performs client-side, U2F-compatible, Webauthn registration.
// This method blocks until either device authentication is successful or the
// context is cancelled. Calling Register without a deadline or cancel condition
// may cause it block forever.
// The caller is expected to react to RegisterPrompt in order to prompt the user
// at appropriate times. Register may choose different flows depending on the
// type of authentication and connected devices.
func Register(
	ctx context.Context,
	origin string, cc *wanlib.CredentialCreation, prompt RegisterPrompt) (*proto.MFARegisterResponse, error) {
	if IsFIDO2Available() {
		log.Debug("FIDO2: Using libfido2 for credential creation")
		return FIDO2Register(ctx, origin, cc, prompt)
	}

	prompt.PromptTouch()
	return U2FRegister(ctx, origin, cc)
}
<commit_msg>Do not dereference assertion before checking for nil (#13761)<commit_after>// Copyright 2022 Gravitational, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package webauthncli

import (
	"context"
	"errors"
	"strings"

	"github.com/gravitational/teleport/api/client/proto"
	"github.com/gravitational/teleport/lib/auth/touchid"
	"github.com/gravitational/trace"

	wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
	log "github.com/sirupsen/logrus"
)

// AuthenticatorAttachment allows callers to choose a specific attachment.
type AuthenticatorAttachment int

const (
	AttachmentAuto AuthenticatorAttachment = iota
	AttachmentCrossPlatform
	AttachmentPlatform
)

// LoginOpts groups non-mandatory options for Login.
type LoginOpts struct {
	// User is the desired credential username for login.
	// If empty, Login may either choose a credential or error due to ambiguity.
	User string
	// OptimisticAssertion allows Login to skip credential listing and attempt
	// to assert directly. The drawback of an optimistic assertion is that the
	// authenticator chooses the login credential, so Login can't guarantee that
	// the User field will be respected. The upside is that it saves a touch for
	// some devices.
	// Login may decide to forego optimistic assertions if it wouldn't save a
	// touch.
	OptimisticAssertion bool
	// AuthenticatorAttachment specifies the desired authenticator attachment.
	AuthenticatorAttachment AuthenticatorAttachment
}

// Login performs client-side, U2F-compatible, Webauthn login.
// This method blocks until either device authentication is successful or the
// context is cancelled. Calling Login without a deadline or cancel condition
// may cause it to block forever.
// The informed user is used to disambiguate credentials in case of passwordless
// logins.
// It returns an MFAAuthenticateResponse and the credential user, if a resident
// credential is used.
// The caller is expected to react to LoginPrompt in order to prompt the user at
// appropriate times. Login may choose different flows depending on the type of
// authentication and connected devices.
func Login(
	ctx context.Context,
	origin string, assertion *wanlib.CredentialAssertion, prompt LoginPrompt, opts *LoginOpts,
) (*proto.MFAAuthenticateResponse, string, error) {
	// origin vs RPID sanity check.
	// Doesn't necessarily means a failure, but it's likely to be one.
	switch {
	case origin == "", assertion == nil: // let downstream handle empty/nil
	case !strings.HasPrefix(origin, "https://"+assertion.Response.RelyingPartyID):
		log.Warnf(""+
			"WebAuthn: origin and RPID mismatch, "+
			"if you are having authentication problems double check your proxy address "+
			"(%q vs %q)", origin, assertion.Response.RelyingPartyID)
	}

	var attachment AuthenticatorAttachment
	var user string
	if opts != nil {
		attachment = opts.AuthenticatorAttachment
		user = opts.User
	}

	switch attachment {
	case AttachmentCrossPlatform:
		log.Debug("Cross-platform login")
		return crossPlatformLogin(ctx, origin, assertion, prompt, opts)
	case AttachmentPlatform:
		log.Debug("Platform login")
		return platformLogin(origin, user, assertion)
	default:
		log.Debug("Attempting platform login")
		resp, credentialUser, err := platformLogin(origin, user, assertion)
		if !errors.Is(err, &touchid.ErrAttemptFailed{}) {
			return resp, credentialUser, trace.Wrap(err)
		}

		log.WithError(err).Debug("Platform login failed, falling back to cross-platform")
		return crossPlatformLogin(ctx, origin, assertion, prompt, opts)
	}
}

func crossPlatformLogin(
	ctx context.Context,
	origin string, assertion *wanlib.CredentialAssertion, prompt LoginPrompt, opts *LoginOpts,
) (*proto.MFAAuthenticateResponse, string, error) {
	if IsFIDO2Available() {
		log.Debug("FIDO2: Using libfido2 for assertion")
		return FIDO2Login(ctx, origin, assertion, prompt, opts)
	}

	prompt.PromptTouch()
	resp, err := U2FLogin(ctx, origin, assertion)
	return resp, "" /* credentialUser */, err
}

func platformLogin(origin, user string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, string, error) {
	resp, credentialUser, err := touchid.AttemptLogin(origin, user, assertion)
	if err != nil {
		return nil, "", err
	}
	return &proto.MFAAuthenticateResponse{
		Response: &proto.MFAAuthenticateResponse_Webauthn{
			Webauthn: wanlib.CredentialAssertionResponseToProto(resp),
		},
	}, credentialUser, nil
}

// Register performs client-side, U2F-compatible, Webauthn registration.
// This method blocks until either device authentication is successful or the
// context is cancelled. Calling Register without a deadline or cancel condition
// may cause it block forever.
// The caller is expected to react to RegisterPrompt in order to prompt the user
// at appropriate times. Register may choose different flows depending on the
// type of authentication and connected devices.
func Register(
	ctx context.Context,
	origin string, cc *wanlib.CredentialCreation, prompt RegisterPrompt) (*proto.MFARegisterResponse, error) {
	if IsFIDO2Available() {
		log.Debug("FIDO2: Using libfido2 for credential creation")
		return FIDO2Register(ctx, origin, cc, prompt)
	}

	prompt.PromptTouch()
	return U2FRegister(ctx, origin, cc)
}
<|endoftext|>


<commit_before>//  Copyright (c) 2014 Couchbase, Inc.
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
//  except in compliance with the License. You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing, software distributed under the
//  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
//  either express or implied. See the License for the specific language governing permissions
//  and limitations under the License.

package upside_down

import (
	"github.com/blevesearch/bleve/index"
	"github.com/blevesearch/bleve/index/store"
)

type UpsideDownCouchTermFieldReader struct {
	indexReader *IndexReader
	iterator    store.KVIterator
	count       uint64
	term        []byte
	field       uint16
}

func newUpsideDownCouchTermFieldReader(indexReader *IndexReader, term []byte, field uint16) (*UpsideDownCouchTermFieldReader, error) {
	dictionaryRow := NewDictionaryRow(term, field, 0)
	val, err := indexReader.kvreader.Get(dictionaryRow.Key())
	if err != nil {
		return nil, err
	}
	if val == nil {
		return &UpsideDownCouchTermFieldReader{
			count: 0,
			term:  term,
			field: field,
		}, nil
	}

	err = dictionaryRow.parseDictionaryV(val)
	if err != nil {
		return nil, err
	}

	tfr := NewTermFrequencyRow(term, field, "", 0, 0)
	it := indexReader.kvreader.PrefixIterator(tfr.Key())

	return &UpsideDownCouchTermFieldReader{
		indexReader: indexReader,
		iterator:    it,
		count:       dictionaryRow.count,
		term:        term,
		field:       field,
	}, nil
}

func (r *UpsideDownCouchTermFieldReader) Count() uint64 {
	return r.count
}

func (r *UpsideDownCouchTermFieldReader) Next() (*index.TermFieldDoc, error) {
	if r.iterator != nil {
		key, val, valid := r.iterator.Current()
		if valid {
			tfr, err := NewTermFrequencyRowKV(key, val)
			if err != nil {
				return nil, err
			}
			r.iterator.Next()
			return &index.TermFieldDoc{
				ID:      string(tfr.doc),
				Freq:    tfr.freq,
				Norm:    float64(tfr.norm),
				Vectors: r.indexReader.index.termFieldVectorsFromTermVectors(tfr.vectors),
			}, nil
		}
	}
	return nil, nil
}

func (r *UpsideDownCouchTermFieldReader) Advance(docID string) (*index.TermFieldDoc, error) {
	if r.iterator != nil {
		tfr := NewTermFrequencyRow(r.term, r.field, docID, 0, 0)
		r.iterator.Seek(tfr.Key())
		key, val, valid := r.iterator.Current()
		if valid {
			tfr, err := NewTermFrequencyRowKV(key, val)
			if err != nil {
				return nil, err
			}
			r.iterator.Next()
			return &index.TermFieldDoc{
				ID:      string(tfr.doc),
				Freq:    tfr.freq,
				Norm:    float64(tfr.norm),
				Vectors: r.indexReader.index.termFieldVectorsFromTermVectors(tfr.vectors),
			}, nil
		}
	}
	return nil, nil
}

func (r *UpsideDownCouchTermFieldReader) Close() error {
	if r.iterator != nil {
		return r.iterator.Close()
	}
	return nil
}

type UpsideDownCouchDocIDReader struct {
	indexReader *IndexReader
	iterator    store.KVIterator
}

func newUpsideDownCouchDocIDReader(indexReader *IndexReader, start, end string) (*UpsideDownCouchDocIDReader, error) {
	if start == "" {
		start = string([]byte{0x0})
	}
	if end == "" {
		end = string([]byte{0xff})
	}
	bisr := NewBackIndexRow(start, nil, nil)
	bier := NewBackIndexRow(end, nil, nil)
	it := indexReader.kvreader.RangeIterator(bisr.Key(), bier.Key())

	return &UpsideDownCouchDocIDReader{
		indexReader: indexReader,
		iterator:    it,
	}, nil
}

func (r *UpsideDownCouchDocIDReader) Next() (string, error) {
	key, val, valid := r.iterator.Current()
	if valid {
		br, err := NewBackIndexRowKV(key, val)
		if err != nil {
			return "", err
		}
		r.iterator.Next()
		return string(br.doc), nil
	}
	return "", nil
}

func (r *UpsideDownCouchDocIDReader) Advance(docID string) (string, error) {
	bir := NewBackIndexRow(docID, nil, nil)
	r.iterator.Seek(bir.Key())
	key, val, valid := r.iterator.Current()
	if valid {
		br, err := NewBackIndexRowKV(key, val)
		if err != nil {
			return "", err
		}
		r.iterator.Next()
		return string(br.doc), nil
	}
	return "", nil
}

func (r *UpsideDownCouchDocIDReader) Close() error {
	return r.iterator.Close()
}
<commit_msg>copy relevant k/v pairs before advancing underlying iterator<commit_after>//  Copyright (c) 2014 Couchbase, Inc.
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
//  except in compliance with the License. You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing, software distributed under the
//  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
//  either express or implied. See the License for the specific language governing permissions
//  and limitations under the License.

package upside_down

import (
	"github.com/blevesearch/bleve/index"
	"github.com/blevesearch/bleve/index/store"
)

type UpsideDownCouchTermFieldReader struct {
	indexReader *IndexReader
	iterator    store.KVIterator
	count       uint64
	term        []byte
	field       uint16
}

func newUpsideDownCouchTermFieldReader(indexReader *IndexReader, term []byte, field uint16) (*UpsideDownCouchTermFieldReader, error) {
	dictionaryRow := NewDictionaryRow(term, field, 0)
	val, err := indexReader.kvreader.Get(dictionaryRow.Key())
	if err != nil {
		return nil, err
	}
	if val == nil {
		return &UpsideDownCouchTermFieldReader{
			count: 0,
			term:  term,
			field: field,
		}, nil
	}

	err = dictionaryRow.parseDictionaryV(val)
	if err != nil {
		return nil, err
	}

	tfr := NewTermFrequencyRow(term, field, "", 0, 0)
	it := indexReader.kvreader.PrefixIterator(tfr.Key())

	return &UpsideDownCouchTermFieldReader{
		indexReader: indexReader,
		iterator:    it,
		count:       dictionaryRow.count,
		term:        term,
		field:       field,
	}, nil
}

func (r *UpsideDownCouchTermFieldReader) Count() uint64 {
	return r.count
}

func (r *UpsideDownCouchTermFieldReader) Next() (*index.TermFieldDoc, error) {
	if r.iterator != nil {
		key, val, valid := r.iterator.Current()
		if valid {
			tfr, err := NewTermFrequencyRowKV(key, val)
			if err != nil {
				return nil, err
			}
			rv := index.TermFieldDoc{
				ID:      string(tfr.doc),
				Freq:    tfr.freq,
				Norm:    float64(tfr.norm),
				Vectors: r.indexReader.index.termFieldVectorsFromTermVectors(tfr.vectors),
			}
			r.iterator.Next()
			return &rv, nil
		}
	}
	return nil, nil
}

func (r *UpsideDownCouchTermFieldReader) Advance(docID string) (*index.TermFieldDoc, error) {
	if r.iterator != nil {
		tfr := NewTermFrequencyRow(r.term, r.field, docID, 0, 0)
		r.iterator.Seek(tfr.Key())
		key, val, valid := r.iterator.Current()
		if valid {
			tfr, err := NewTermFrequencyRowKV(key, val)
			if err != nil {
				return nil, err
			}
			rv := index.TermFieldDoc{
				ID:      string(tfr.doc),
				Freq:    tfr.freq,
				Norm:    float64(tfr.norm),
				Vectors: r.indexReader.index.termFieldVectorsFromTermVectors(tfr.vectors),
			}
			r.iterator.Next()
			return &rv, nil
		}
	}
	return nil, nil
}

func (r *UpsideDownCouchTermFieldReader) Close() error {
	if r.iterator != nil {
		return r.iterator.Close()
	}
	return nil
}

type UpsideDownCouchDocIDReader struct {
	indexReader *IndexReader
	iterator    store.KVIterator
}

func newUpsideDownCouchDocIDReader(indexReader *IndexReader, start, end string) (*UpsideDownCouchDocIDReader, error) {
	if start == "" {
		start = string([]byte{0x0})
	}
	if end == "" {
		end = string([]byte{0xff})
	}
	bisr := NewBackIndexRow(start, nil, nil)
	bier := NewBackIndexRow(end, nil, nil)
	it := indexReader.kvreader.RangeIterator(bisr.Key(), bier.Key())

	return &UpsideDownCouchDocIDReader{
		indexReader: indexReader,
		iterator:    it,
	}, nil
}

func (r *UpsideDownCouchDocIDReader) Next() (string, error) {
	key, val, valid := r.iterator.Current()
	if valid {
		br, err := NewBackIndexRowKV(key, val)
		if err != nil {
			return "", err
		}
		rv := string(br.doc)
		r.iterator.Next()
		return rv, nil
	}
	return "", nil
}

func (r *UpsideDownCouchDocIDReader) Advance(docID string) (string, error) {
	bir := NewBackIndexRow(docID, nil, nil)
	r.iterator.Seek(bir.Key())
	key, val, valid := r.iterator.Current()
	if valid {
		br, err := NewBackIndexRowKV(key, val)
		if err != nil {
			return "", err
		}
		rv := string(br.doc)
		r.iterator.Next()
		return rv, nil
	}
	return "", nil
}

func (r *UpsideDownCouchDocIDReader) Close() error {
	return r.iterator.Close()
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013-2014, Jeremy Bingham (<jbingham@gmail.com>)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package indexer

import (
	"github.com/ctdk/goiardi/datastore"
	"github.com/ctdk/goiardi/util"
)

type PostgresIndex struct {

}

func (p *PostgresIndex) Initialize() error {
	// check if the default indexes exist yet, and if not create them
	return nil
}

func (p *PostgresIndex) CreateCollection(col string) error {
	sqlStmt := "INSERT INTO goiardi.search_collections (name, organization_id) VALUES ($1, $2)"
	tx, err := datastore.Dbh.Begin()
	if err != nil {
		return err
	}
	_, err = tx.Exec(sqlStmt, col, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()
	return nil
}

func (p *PostgresIndex) DeleteCollection(col string) error {
	tx, err := datastore.Dbh.Begin()
	if err != nil {
		return err
	}
	_, err = tx.Exec("SELECT goiardi.delete_search_collection($1, $2)", col, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()
	return nil
}

func (p *PostgresIndex) DeleteItem(idxName string, doc string) error {
	tx, err := datastore.Dbh.Begin()
	if err != nil {
		return err
	}
	_, err = tx.Exec("SELECT goiardi.delete_search_item($1, $2)", idxName, doc, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()
	return nil
}

func (p *PostgresIndex) SaveItem(obj Indexable) error {

	return nil
}

func (p *PostgresIndex) Endpoints() ([]string, error) {
	sqlStmt := "SELECT ARRAY_AGG(name) FROM goiardi.search_collections WHERE organization_id = $1"
	stmt, err := datastore.Dbh.Prepare(sqlStmt)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	var endpoints util.StringSlice
	err = stmt.QueryRow(1).Scan(&endpoints)
	if err != nil {
		return nil, err
	}

	return endpoints, nil
}

func (p *PostgresIndex) Clear() error {
	tx, err := datastore.Dbh.Begin()
	if err != nil {
		return err
	}
	sqlStmt := "DELETE FROM goiardi.search_items WHERE organization_id = $1"
	_, err = tx.Exec(sqlStmt, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	sqlStmt = "DELETE FROM goiardi.search_collections WHERE organization_id = $1"
	_, err = tx.Exec(sqlStmt, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	sqlStmt = "INSERT INTO goiardi.search_collections (name, organization_id) VALUES ('client', $1), ('environment', $1), ('node', $1), ('role', $1)"
	_, err = tx.Exec(sqlStmt, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()

	return nil
}
<commit_msg>First, untested pass at code for saving flattened items to the search table<commit_after>/*
 * Copyright (c) 2013-2014, Jeremy Bingham (<jbingham@gmail.com>)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package indexer

import (
	"fmt"
	"github.com/ctdk/goiardi/datastore"
	"github.com/ctdk/goiardi/util"
	"github.com/lib/pq"
)

type PostgresIndex struct {

}

func (p *PostgresIndex) Initialize() error {
	// check if the default indexes exist yet, and if not create them
	return nil
}

func (p *PostgresIndex) CreateCollection(col string) error {
	sqlStmt := "INSERT INTO goiardi.search_collections (name, organization_id) VALUES ($1, $2)"
	tx, err := datastore.Dbh.Begin()
	if err != nil {
		return err
	}
	_, err = tx.Exec(sqlStmt, col, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()
	return nil
}

func (p *PostgresIndex) DeleteCollection(col string) error {
	tx, err := datastore.Dbh.Begin()
	if err != nil {
		return err
	}
	_, err = tx.Exec("SELECT goiardi.delete_search_collection($1, $2)", col, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()
	return nil
}

func (p *PostgresIndex) DeleteItem(idxName string, doc string) error {
	tx, err := datastore.Dbh.Begin()
	if err != nil {
		return err
	}
	_, err = tx.Exec("SELECT goiardi.delete_search_item($1, $2)", idxName, doc, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()
	return nil
}

func (p *PostgresIndex) SaveItem(obj Indexable) error {
	flat := obj.Flatten()
	itemName := obj.DocID()
	collectionName := obj.Index()
	tx, err := datastore.Dbh.Begin()
	if err != nil {
		return err
	}
	var scID int32
	err = tx.QueryRow("SELECT id FROM goiardi.search_collections WHERE organization_id = $1 AND name = $2", 1, collectionName).Scan(&scID)
	if err != nil {
		tx.Rollback()
		return err
	}
	stmt, err := tx.Prepare(pq.CopyIn("goiardi.search_items", "organization_id", "search_collection_id", "item_name", "value", "path"))
	if err != nil {
		tx.Rollback()
		return err
	}
	defer stmt.Close()
	for k, v := range flat {
		// will the values need escaped like in file search?
		switch v := v.(type) {
		case string:
			_, err = stmt.Exec(1, scID, itemName, v, k)
			if err != nil {
				tx.Rollback()
				return err
			}
		case []string:
			for _, w := range v {
				_, err = stmt.Exec(1, scID, itemName, w, k)
				if err != nil {
					tx.Rollback()
					return err
				}
			}
		default:
			err = fmt.Errorf("pg search should have never been able to reach this state. Key %s had a value %v of type %T", k, v, v)
			tx.Rollback()
			return err
		}
	}
	_, err = stmt.Exec()
	if err != nil {
		tx.Rollback()
		return err
	}
	err = tx.Commit()
	if err != nil {
		return err
	}
	return nil
}

func (p *PostgresIndex) Endpoints() ([]string, error) {
	sqlStmt := "SELECT ARRAY_AGG(name) FROM goiardi.search_collections WHERE organization_id = $1"
	stmt, err := datastore.Dbh.Prepare(sqlStmt)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	var endpoints util.StringSlice
	err = stmt.QueryRow(1).Scan(&endpoints)
	if err != nil {
		return nil, err
	}

	return endpoints, nil
}

func (p *PostgresIndex) Clear() error {
	tx, err := datastore.Dbh.Begin()
	if err != nil {
		return err
	}
	sqlStmt := "DELETE FROM goiardi.search_items WHERE organization_id = $1"
	_, err = tx.Exec(sqlStmt, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	sqlStmt = "DELETE FROM goiardi.search_collections WHERE organization_id = $1"
	_, err = tx.Exec(sqlStmt, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	sqlStmt = "INSERT INTO goiardi.search_collections (name, organization_id) VALUES ('client', $1), ('environment', $1), ('node', $1), ('role', $1)"
	_, err = tx.Exec(sqlStmt, 1)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()

	return nil
}
<|endoftext|>


<commit_before>package config

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

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

func TestDefaultValues(t *testing.T) {
	c := &Config{}
	c.home = "/home/alice"
	c.configure()
	assert.Equal(t, "", c.APIKey)
	assert.Equal(t, "http://exercism.io", c.API)
	assert.Equal(t, filepath.FromSlash("/home/alice/exercism"), c.Dir)
}

func TestCustomValues(t *testing.T) {
	c := &Config{
		APIKey: "abc123",
		API:    "http://example.org",
		Dir:    "/path/to/exercises",
		XAPI:   "http://x.example.org",
	}
	c.configure()
	assert.Equal(t, "abc123", c.APIKey)
	assert.Equal(t, "http://example.org", c.API)
	assert.Equal(t, "/path/to/exercises", c.Dir)
	assert.Equal(t, "http://x.example.org", c.XAPI)
}

func TestExpandHomeDir(t *testing.T) {
	c := &Config{Dir: "~/practice"}
	c.home = "/home/alice"
	c.configure()
	assert.Equal(t, "/home/alice/practice", c.Dir)
}

func TestExpandConfigPath(t *testing.T) {
	testCases := []struct {
		path   string
		env    string
		result string
	}{
		{
			"path/to/config.json",
			"",
			"path/to/config.json",
		},
		{
			"",
			"~/config.json",
			"/home/alice/config.json",
		},
		{
			"",
			"",
			"/home/alice/.exercism.json",
		},
	}
	home := "/home/alice"

	for _, tt := range testCases {
		assert.Equal(t, Expand(tt.path, tt.env, home), tt.result)
	}
}

func TestSanitizeWhitespace(t *testing.T) {
	c := &Config{
		APIKey: "   abc123\n\r\n  ",
		API:    "       ",
		Dir:    "  \r\n/path/to/exercises   \r\n",
		XAPI:   "   ",
	}
	c.configure()
	assert.Equal(t, "abc123", c.APIKey)
	assert.Equal(t, "http://exercism.io", c.API)
	assert.Equal(t, "/path/to/exercises", c.Dir)
	assert.Equal(t, "http://x.exercism.io", c.XAPI)
}

func TestReadNonexistantConfig(t *testing.T) {
	c, err := Read("/no/such/config.json")
	assert.NoError(t, err)
	assert.Equal(t, c.APIKey, "")
	assert.Equal(t, c.API, "http://exercism.io")
	assert.Equal(t, c.XAPI, "http://x.exercism.io")
	assert.False(t, c.IsAuthenticated())
	if !strings.HasSuffix(c.Dir, filepath.FromSlash("/exercism")) {
		t.Fatal("Default unconfigured config should use home dir")
	}
}

func TestReadingWritingConfig(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "")
	filename := fmt.Sprintf("%s/%s", tmpDir, File)
	assert.NoError(t, err)

	c1 := &Config{
		APIKey: "MyKey",
		Dir:    "/exercism/directory",
		API:    "localhost",
		XAPI:   "localhost",
		File: filename,
	}
	c1.configure()

	c1.Write()

	c2, err := Read(filename)
	assert.NoError(t, err)

	assert.Equal(t, c1.APIKey, c2.APIKey)
	assert.Equal(t, c1.Dir, c2.Dir)
	assert.Equal(t, c1.API, c2.API)
	assert.Equal(t, c1.XAPI, c2.XAPI)
}

func TestUpdateConfig(t *testing.T) {
	c := &Config{
		APIKey: "MyKey",
		Dir:    "/exercism/directory",
		API:    "localhost",
		XAPI:   "localhost",
	}

	c.Update("NewKey", "", "", "")
	assert.Equal(t, "NewKey", c.APIKey)
	assert.Equal(t, "localhost", c.API)
	assert.Equal(t, "/exercism/directory", c.Dir)
	assert.Equal(t, "localhost", c.XAPI)

	c.Update("", "http://example.com", "", "")
	assert.Equal(t, "NewKey", c.APIKey)
	assert.Equal(t, "http://example.com", c.API)
	assert.Equal(t, "/exercism/directory", c.Dir)
	assert.Equal(t, "localhost", c.XAPI)

	c.Update("", "", "/tmp/exercism", "")
	assert.Equal(t, "NewKey", c.APIKey)
	assert.Equal(t, "http://example.com", c.API)
	assert.Equal(t, "/tmp/exercism", c.Dir)
	assert.Equal(t, "localhost", c.XAPI)

	c.Update("", "", "", "http://x.example.org")
	assert.Equal(t, "NewKey", c.APIKey)
	assert.Equal(t, "http://example.com", c.API)
	assert.Equal(t, "/tmp/exercism", c.Dir)
	assert.Equal(t, "http://x.example.org", c.XAPI)
}

func TestReadDefaultConfig(t *testing.T) {
	dir, err := filepath.Abs("../fixtures/home")
	assert.NoError(t, err)

	c := &Config{home: dir}
	err = c.Read("")
	assert.NoError(t, err)
	assert.Equal(t, "abc123", c.APIKey)
	assert.Equal(t, "/path/to/exercism", c.Dir)
	assert.Equal(t, "http://example.com", c.API)
	assert.Equal(t, "http://x.example.com", c.XAPI)
}

func TestReadCustomConfig(t *testing.T) {
	dir, err := filepath.Abs("../fixtures/home/")
	assert.NoError(t, err)

	c := &Config{home: dir}
	file := fmt.Sprintf("%s/custom.json", dir)
	err = c.Read(file)
	assert.NoError(t, err)
	assert.Equal(t, "xyz000", c.APIKey)
	assert.Equal(t, "/tmp/exercism", c.Dir)
	assert.Equal(t, "http://example.org", c.API)
	assert.Equal(t, "http://x.example.org", c.XAPI)
}

func TestReadLegacyConfig(t *testing.T) {
	dir, err := filepath.Abs("../fixtures/home/")
	assert.NoError(t, err)

	c := &Config{home: dir}
	file := fmt.Sprintf("%s/legacy.json", dir)
	err = c.Read(file)
	assert.NoError(t, err)
	assert.Equal(t, "prq567", c.APIKey)
	assert.Equal(t, "/tmp/stuff", c.Dir)
	assert.Equal(t, "http://api.example.com", c.API)
	assert.Equal(t, "http://problems.example.com", c.XAPI)
}

func TestConfigInEnv(t *testing.T) {
	_, caller, _, ok := runtime.Caller(0)
	assert.True(t, ok)
	file := filepath.Join(filepath.Dir(caller), "..", "fixtures", "special.json")
	os.Setenv(fileEnvKey, file)

	c := &Config{home: "/tmp/home"}
	err := c.Read("")
	assert.NoError(t, err)
	assert.Equal(t, "abc123", c.APIKey)
	assert.Equal(t, "/a/b/c", c.Dir)
	assert.Equal(t, "http://api.example.com", c.API)
	assert.Equal(t, "http://x.example.com", c.XAPI)
}
<commit_msg>remove redundant tests<commit_after>package config

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

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

func TestDefaultValues(t *testing.T) {
	c := &Config{}
	c.home = "/home/alice"
	c.configure()
	assert.Equal(t, "", c.APIKey)
	assert.Equal(t, "http://exercism.io", c.API)
	assert.Equal(t, filepath.FromSlash("/home/alice/exercism"), c.Dir)
}

func TestCustomValues(t *testing.T) {
	c := &Config{
		APIKey: "abc123",
		API:    "http://example.org",
		Dir:    "/path/to/exercises",
		XAPI:   "http://x.example.org",
	}
	c.configure()
	assert.Equal(t, "abc123", c.APIKey)
	assert.Equal(t, "http://example.org", c.API)
	assert.Equal(t, "/path/to/exercises", c.Dir)
	assert.Equal(t, "http://x.example.org", c.XAPI)
}

func TestExpandHomeDir(t *testing.T) {
	c := &Config{Dir: "~/practice"}
	c.home = "/home/alice"
	c.configure()
	assert.Equal(t, "/home/alice/practice", c.Dir)
}

func TestExpandConfigPath(t *testing.T) {
	testCases := []struct {
		path   string
		env    string
		result string
	}{
		{
			"path/to/config.json",
			"",
			"path/to/config.json",
		},
		{
			"",
			"~/config.json",
			"/home/alice/config.json",
		},
		{
			"",
			"",
			"/home/alice/.exercism.json",
		},
	}
	home := "/home/alice"

	for _, tt := range testCases {
		assert.Equal(t, Expand(tt.path, tt.env, home), tt.result)
	}
}

func TestSanitizeWhitespace(t *testing.T) {
	c := &Config{
		APIKey: "   abc123\n\r\n  ",
		API:    "       ",
		Dir:    "  \r\n/path/to/exercises   \r\n",
		XAPI:   "   ",
	}
	c.configure()
	assert.Equal(t, "abc123", c.APIKey)
	assert.Equal(t, "http://exercism.io", c.API)
	assert.Equal(t, "/path/to/exercises", c.Dir)
	assert.Equal(t, "http://x.exercism.io", c.XAPI)
}

func TestReadNonexistantConfig(t *testing.T) {
	c, err := Read("/no/such/config.json")
	assert.NoError(t, err)
	assert.Equal(t, c.APIKey, "")
	assert.Equal(t, c.API, "http://exercism.io")
	assert.Equal(t, c.XAPI, "http://x.exercism.io")
	assert.False(t, c.IsAuthenticated())
	if !strings.HasSuffix(c.Dir, filepath.FromSlash("/exercism")) {
		t.Fatal("Default unconfigured config should use home dir")
	}
}

func TestReadingWritingConfig(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "")
	filename := fmt.Sprintf("%s/%s", tmpDir, File)
	assert.NoError(t, err)

	c1 := &Config{
		APIKey: "MyKey",
		Dir:    "/exercism/directory",
		API:    "localhost",
		XAPI:   "localhost",
		File: filename,
	}
	c1.configure()

	c1.Write()

	c2, err := Read(filename)
	assert.NoError(t, err)

	assert.Equal(t, c1.APIKey, c2.APIKey)
	assert.Equal(t, c1.Dir, c2.Dir)
	assert.Equal(t, c1.API, c2.API)
	assert.Equal(t, c1.XAPI, c2.XAPI)
}

func TestUpdateConfig(t *testing.T) {
	c := &Config{
		APIKey: "MyKey",
		API:    "localhost",
		Dir:    "/exercism/directory",
		XAPI:   "localhost",
	}

	// Test the blank values don't overwrite existing values
	c.Update("", "", "", "")
	assert.Equal(t, "MyKey", c.APIKey)
	assert.Equal(t, "localhost", c.API)
	assert.Equal(t, "/exercism/directory", c.Dir)
	assert.Equal(t, "localhost", c.XAPI)

	// Test that each value can be overwritten
	c.Update("NewKey", "http://example.com", "/tmp/exercism", "http://x.example.org")
	assert.Equal(t, "NewKey", c.APIKey)
	assert.Equal(t, "http://example.com", c.API)
	assert.Equal(t, "/tmp/exercism", c.Dir)
	assert.Equal(t, "http://x.example.org", c.XAPI)
}

func TestReadDefaultConfig(t *testing.T) {
	dir, err := filepath.Abs("../fixtures/home")
	assert.NoError(t, err)

	c := &Config{home: dir}
	err = c.Read("")
	assert.NoError(t, err)
	assert.Equal(t, "abc123", c.APIKey)
	assert.Equal(t, "/path/to/exercism", c.Dir)
	assert.Equal(t, "http://example.com", c.API)
	assert.Equal(t, "http://x.example.com", c.XAPI)
}

func TestReadCustomConfig(t *testing.T) {
	dir, err := filepath.Abs("../fixtures/home/")
	assert.NoError(t, err)

	c := &Config{home: dir}
	file := fmt.Sprintf("%s/custom.json", dir)
	err = c.Read(file)
	assert.NoError(t, err)
	assert.Equal(t, "xyz000", c.APIKey)
	assert.Equal(t, "/tmp/exercism", c.Dir)
	assert.Equal(t, "http://example.org", c.API)
	assert.Equal(t, "http://x.example.org", c.XAPI)
}

func TestReadLegacyConfig(t *testing.T) {
	dir, err := filepath.Abs("../fixtures/home/")
	assert.NoError(t, err)

	c := &Config{home: dir}
	file := fmt.Sprintf("%s/legacy.json", dir)
	err = c.Read(file)
	assert.NoError(t, err)
	assert.Equal(t, "prq567", c.APIKey)
	assert.Equal(t, "/tmp/stuff", c.Dir)
	assert.Equal(t, "http://api.example.com", c.API)
	assert.Equal(t, "http://problems.example.com", c.XAPI)
}

func TestConfigInEnv(t *testing.T) {
	_, caller, _, ok := runtime.Caller(0)
	assert.True(t, ok)
	file := filepath.Join(filepath.Dir(caller), "..", "fixtures", "special.json")
	os.Setenv(fileEnvKey, file)

	c := &Config{home: "/tmp/home"}
	err := c.Read("")
	assert.NoError(t, err)
	assert.Equal(t, "abc123", c.APIKey)
	assert.Equal(t, "/a/b/c", c.Dir)
	assert.Equal(t, "http://api.example.com", c.API)
	assert.Equal(t, "http://x.example.com", c.XAPI)
}
<|endoftext|>


<commit_before>package config

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

var sampleConfig = `
apikey = "abcde"
display_name = "fghij"
diagnostic = true

[connection]
post_metrics_retry_delay_seconds = 600
post_metrics_retry_max = 5

[plugin.metrics.mysql]
command = "ruby /path/to/your/plugin/mysql.rb"

[sensu.checks.memory] # for backward compatibility
command = "ruby ../sensu/plugins/system/memory-metrics.rb"
type = "metric"

[plugin.checks.heartbeat]
command = "heartbeat.sh"
notification_interval = 60
`

func TestLoadConfig(t *testing.T) {
	tmpFile, err := ioutil.TempFile("", "")
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	if err = ioutil.WriteFile(tmpFile.Name(), []byte(sampleConfig), 0644); err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	config, err := LoadConfig(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apibase != "https://mackerel.io" {
		t.Error("should be https://mackerel.io (arg value should be used)")
	}

	if config.Apikey != "abcde" {
		t.Error("should be abcde (config value should be used)")
	}

	if config.DisplayName != "fghij" {
		t.Error("should be fghij (config value should be used)")
	}

	if config.Diagnostic != true {
		t.Error("should be true (config value should be used)")
	}

	if config.Connection.PostMetricsDequeueDelaySeconds != 30 {
		t.Error("should be 30 (default value should be used)")
	}

	if config.Connection.PostMetricsRetryDelaySeconds != 180 {
		t.Error("should be 180 (max retry delay seconds is 180)")
	}

	if config.Connection.PostMetricsRetryMax != 5 {
		t.Error("should be 5 (config value should be used)")
	}
}

var sampleConfigWithHostStatus = `
apikey = "abcde"
display_name = "fghij"

[host_status]
on_start = "working"
on_stop  = "poweroff"
`

func TestLoadConfigWithHostStatus(t *testing.T) {
	tmpFile, err := ioutil.TempFile("", "")
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	if err = ioutil.WriteFile(tmpFile.Name(), []byte(sampleConfigWithHostStatus), 0644); err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	config, err := LoadConfig(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apikey != "abcde" {
		t.Error("should be abcde (config value should be used)")
	}

	if config.DisplayName != "fghij" {
		t.Error("should be fghij (config value should be used)")
	}

	if config.HostStatus.OnStart != "working" {
		t.Error(`HostStatus.OnStart should be "working"`)
	}

	if config.HostStatus.OnStop != "poweroff" {
		t.Error(`HostStatus.OnStop should be "poweroff"`)
	}
}

func TestLoadConfigFile(t *testing.T) {
	tmpFile, err := ioutil.TempFile("", "mackerel-config-test")
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	if _, err := tmpFile.WriteString(sampleConfig); err != nil {
		t.Fatal("should not raise error")
	}
	tmpFile.Sync()
	tmpFile.Close()
	defer os.Remove(tmpFile.Name())

	config, err := loadConfigFile(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apikey != "abcde" {
		t.Error("Apikey should be abcde")
	}

	if config.DisplayName != "fghij" {
		t.Error("DisplayName should be fghij")
	}

	if config.Diagnostic != true {
		t.Error("Diagnostic should be true")
	}

	if config.Connection.PostMetricsRetryMax != 5 {
		t.Error("PostMetricsRetryMax should be 5")
	}

	if config.Plugin["metrics"] == nil {
		t.Error("plugin should have metrics")
	}
	pluginConf := config.Plugin["metrics"]["mysql"]
	if pluginConf.Command != "ruby /path/to/your/plugin/mysql.rb" {
		t.Errorf("plugin conf command should be 'ruby /path/to/your/plugin/mysql.rb' but %v", pluginConf.Command)
	}

	// for backward compatibility
	sensu := config.Plugin["metrics"]["DEPRECATED-sensu-memory"]
	if sensu.Command != "ruby ../sensu/plugins/system/memory-metrics.rb" {
		t.Error("sensu command should be 'ruby ../sensu/plugins/system/memory-metrics.rb'")
	}

	if config.Plugin["checks"] == nil {
		t.Error("plugin should have checks")
	}
	checks := config.Plugin["checks"]["heartbeat"]
	if checks.Command != "heartbeat.sh" {
		t.Error("sensu command should be 'heartbeat.sh'")
	}
	if *checks.NotificationInterval != 60 {
		t.Error("notification_interval should be 60")
	}
}

func assertNoError(t *testing.T, err error) {
	if err != nil {
		t.Error(err)
	}
}

func assert(t *testing.T, ok bool, msg string) {
	if !ok {
		t.Error(msg)
	}
}

var tomlQuotedReplacer = strings.NewReplacer(
	"\t", "\\t",
	"\n", "\\n",
	"\r", "\\r",
	"\"", "\\\"",
	"\\", "\\\\",
)

func TestLoadConfigFileInclude(t *testing.T) {
	configDir, err := ioutil.TempDir("", "mackerel-config-test")
	assertNoError(t, err)

	configFile, err := ioutil.TempFile("", "mackerel-config-test")
	assertNoError(t, err)

	includedFile, err := os.Create(filepath.Join(configDir, "sub1.conf"))

	configContent := fmt.Sprintf(`
apikey = "not overwritten"
roles = [ "roles", "to be overwritten" ]

include = "%s/*.conf"

[plugin.metrics.foo1]
command = "foo1"

[plugin.metrics.bar]
command = "this wille be overwritten"
`, tomlQuotedReplacer.Replace(configDir))

	includedContent := `
roles = [ "Service:role" ]

[plugin.metrics.foo2]
command = "foo2"

[plugin.metrics.bar]
command = "bar"
`

	_, err = configFile.WriteString(configContent)
	assertNoError(t, err)

	_, err = includedFile.WriteString(includedContent)
	assertNoError(t, err)

	configFile.Close()
	includedFile.Close()
	defer os.Remove(configFile.Name())
	defer os.Remove(includedFile.Name())

	config, err := loadConfigFile(configFile.Name())
	assertNoError(t, err)

	assert(t, config.Apikey == "not overwritten", "apikey should not be overwritten")
	assert(t, len(config.Roles) == 1, "roles should be overwritten")
	assert(t, config.Roles[0] == "Service:role", "roles should be overwritten")
	assert(t, config.Plugin["metrics"]["foo1"].Command == "foo1", "plugin.metrics.foo1 should exist")
	assert(t, config.Plugin["metrics"]["foo2"].Command == "foo2", "plugin.metrics.foo2 should exist")
	assert(t, config.Plugin["metrics"]["bar"].Command == "bar", "plugin.metrics.bar should be overwritten")
}

func TestFileSystemHostIDStorage(t *testing.T) {
	root, err := ioutil.TempDir("", "mackerel-agent-test")
	if err != nil {
		t.Fatal(err)
	}

	s := FileSystemHostIDStorage{Root: root}
	err = s.SaveHostID("test-host-id")
	assertNoError(t, err)

	hostID, err := s.LoadHostID()
	assertNoError(t, err)
	assert(t, hostID == "test-host-id", "SaveHostID and LoadHostID should preserve the host id")

	err = s.DeleteSavedHostID()
	assertNoError(t, err)

	_, err = s.LoadHostID()
	assert(t, err != nil, "LoadHostID after DeleteSavedHostID must fail")
}

func TestConfig_HostIDStorage(t *testing.T) {
	conf := Config{
		Root: "test-root",
	}

	storage, ok := conf.hostIDStorage().(*FileSystemHostIDStorage)
	assert(t, ok, "Default hostIDStorage must be *FileSystemHostIDStorage")
	assert(t, storage.Root == "test-root", "FileSystemHostIDStorage must have the same Root of Config")
}
<commit_msg>add a test for max_check_attempts<commit_after>package config

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

var sampleConfig = `
apikey = "abcde"
display_name = "fghij"
diagnostic = true

[connection]
post_metrics_retry_delay_seconds = 600
post_metrics_retry_max = 5

[plugin.metrics.mysql]
command = "ruby /path/to/your/plugin/mysql.rb"

[sensu.checks.memory] # for backward compatibility
command = "ruby ../sensu/plugins/system/memory-metrics.rb"
type = "metric"

[plugin.checks.heartbeat]
command = "heartbeat.sh"
notification_interval = 60
max_check_attempts = 3
`

func TestLoadConfig(t *testing.T) {
	tmpFile, err := ioutil.TempFile("", "")
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	if err = ioutil.WriteFile(tmpFile.Name(), []byte(sampleConfig), 0644); err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	config, err := LoadConfig(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apibase != "https://mackerel.io" {
		t.Error("should be https://mackerel.io (arg value should be used)")
	}

	if config.Apikey != "abcde" {
		t.Error("should be abcde (config value should be used)")
	}

	if config.DisplayName != "fghij" {
		t.Error("should be fghij (config value should be used)")
	}

	if config.Diagnostic != true {
		t.Error("should be true (config value should be used)")
	}

	if config.Connection.PostMetricsDequeueDelaySeconds != 30 {
		t.Error("should be 30 (default value should be used)")
	}

	if config.Connection.PostMetricsRetryDelaySeconds != 180 {
		t.Error("should be 180 (max retry delay seconds is 180)")
	}

	if config.Connection.PostMetricsRetryMax != 5 {
		t.Error("should be 5 (config value should be used)")
	}
}

var sampleConfigWithHostStatus = `
apikey = "abcde"
display_name = "fghij"

[host_status]
on_start = "working"
on_stop  = "poweroff"
`

func TestLoadConfigWithHostStatus(t *testing.T) {
	tmpFile, err := ioutil.TempFile("", "")
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	if err = ioutil.WriteFile(tmpFile.Name(), []byte(sampleConfigWithHostStatus), 0644); err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	config, err := LoadConfig(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apikey != "abcde" {
		t.Error("should be abcde (config value should be used)")
	}

	if config.DisplayName != "fghij" {
		t.Error("should be fghij (config value should be used)")
	}

	if config.HostStatus.OnStart != "working" {
		t.Error(`HostStatus.OnStart should be "working"`)
	}

	if config.HostStatus.OnStop != "poweroff" {
		t.Error(`HostStatus.OnStop should be "poweroff"`)
	}
}

func TestLoadConfigFile(t *testing.T) {
	tmpFile, err := ioutil.TempFile("", "mackerel-config-test")
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}
	if _, err := tmpFile.WriteString(sampleConfig); err != nil {
		t.Fatal("should not raise error")
	}
	tmpFile.Sync()
	tmpFile.Close()
	defer os.Remove(tmpFile.Name())

	config, err := loadConfigFile(tmpFile.Name())
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	if config.Apikey != "abcde" {
		t.Error("Apikey should be abcde")
	}

	if config.DisplayName != "fghij" {
		t.Error("DisplayName should be fghij")
	}

	if config.Diagnostic != true {
		t.Error("Diagnostic should be true")
	}

	if config.Connection.PostMetricsRetryMax != 5 {
		t.Error("PostMetricsRetryMax should be 5")
	}

	if config.Plugin["metrics"] == nil {
		t.Error("plugin should have metrics")
	}
	pluginConf := config.Plugin["metrics"]["mysql"]
	if pluginConf.Command != "ruby /path/to/your/plugin/mysql.rb" {
		t.Errorf("plugin conf command should be 'ruby /path/to/your/plugin/mysql.rb' but %v", pluginConf.Command)
	}

	// for backward compatibility
	sensu := config.Plugin["metrics"]["DEPRECATED-sensu-memory"]
	if sensu.Command != "ruby ../sensu/plugins/system/memory-metrics.rb" {
		t.Error("sensu command should be 'ruby ../sensu/plugins/system/memory-metrics.rb'")
	}

	if config.Plugin["checks"] == nil {
		t.Error("plugin should have checks")
	}
	checks := config.Plugin["checks"]["heartbeat"]
	if checks.Command != "heartbeat.sh" {
		t.Error("sensu command should be 'heartbeat.sh'")
	}
	if *checks.NotificationInterval != 60 {
		t.Error("notification_interval should be 60")
	}
	if *checks.MaxCheckAttempts != 3 {
		t.Error("max_check_attempts should be 3")
	}
}

func assertNoError(t *testing.T, err error) {
	if err != nil {
		t.Error(err)
	}
}

func assert(t *testing.T, ok bool, msg string) {
	if !ok {
		t.Error(msg)
	}
}

var tomlQuotedReplacer = strings.NewReplacer(
	"\t", "\\t",
	"\n", "\\n",
	"\r", "\\r",
	"\"", "\\\"",
	"\\", "\\\\",
)

func TestLoadConfigFileInclude(t *testing.T) {
	configDir, err := ioutil.TempDir("", "mackerel-config-test")
	assertNoError(t, err)

	configFile, err := ioutil.TempFile("", "mackerel-config-test")
	assertNoError(t, err)

	includedFile, err := os.Create(filepath.Join(configDir, "sub1.conf"))

	configContent := fmt.Sprintf(`
apikey = "not overwritten"
roles = [ "roles", "to be overwritten" ]

include = "%s/*.conf"

[plugin.metrics.foo1]
command = "foo1"

[plugin.metrics.bar]
command = "this wille be overwritten"
`, tomlQuotedReplacer.Replace(configDir))

	includedContent := `
roles = [ "Service:role" ]

[plugin.metrics.foo2]
command = "foo2"

[plugin.metrics.bar]
command = "bar"
`

	_, err = configFile.WriteString(configContent)
	assertNoError(t, err)

	_, err = includedFile.WriteString(includedContent)
	assertNoError(t, err)

	configFile.Close()
	includedFile.Close()
	defer os.Remove(configFile.Name())
	defer os.Remove(includedFile.Name())

	config, err := loadConfigFile(configFile.Name())
	assertNoError(t, err)

	assert(t, config.Apikey == "not overwritten", "apikey should not be overwritten")
	assert(t, len(config.Roles) == 1, "roles should be overwritten")
	assert(t, config.Roles[0] == "Service:role", "roles should be overwritten")
	assert(t, config.Plugin["metrics"]["foo1"].Command == "foo1", "plugin.metrics.foo1 should exist")
	assert(t, config.Plugin["metrics"]["foo2"].Command == "foo2", "plugin.metrics.foo2 should exist")
	assert(t, config.Plugin["metrics"]["bar"].Command == "bar", "plugin.metrics.bar should be overwritten")
}

func TestFileSystemHostIDStorage(t *testing.T) {
	root, err := ioutil.TempDir("", "mackerel-agent-test")
	if err != nil {
		t.Fatal(err)
	}

	s := FileSystemHostIDStorage{Root: root}
	err = s.SaveHostID("test-host-id")
	assertNoError(t, err)

	hostID, err := s.LoadHostID()
	assertNoError(t, err)
	assert(t, hostID == "test-host-id", "SaveHostID and LoadHostID should preserve the host id")

	err = s.DeleteSavedHostID()
	assertNoError(t, err)

	_, err = s.LoadHostID()
	assert(t, err != nil, "LoadHostID after DeleteSavedHostID must fail")
}

func TestConfig_HostIDStorage(t *testing.T) {
	conf := Config{
		Root: "test-root",
	}

	storage, ok := conf.hostIDStorage().(*FileSystemHostIDStorage)
	assert(t, ok, "Default hostIDStorage must be *FileSystemHostIDStorage")
	assert(t, storage.Root == "test-root", "FileSystemHostIDStorage must have the same Root of Config")
}
<|endoftext|>


<commit_before>// TODO: write package comment
package channel

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
)

const (
	team   = "monkeytacos"
	apiURL = "https://%s.slack.com/api/%s?%s"
)

var apiToken string

type channelListResponse struct {
	Channels []Channel `json:"channels"`
	Ok       bool      `json:"ok"`
	Err      string    `json:"error,omitempty"`
}

type channelResponse struct {
	Ok      bool    `json:"ok"`
	Channel Channel `json:"channel"`
	Err     string  `json:"error,omitempty"`
}

type Channel struct {
	Id      string   `json:"id"`
	Name    string   `json:"name"`
	Members []string `json:"members"`
}

func init() {
	apiToken = os.Getenv("SLACK_API_TOKEN")
	if apiToken == "" {
		log.Fatal("SLACK_API_TOKEN not set")
	}
}

func New(name string) (Channel, error) {
	var emptyChannel Channel

	qsp := map[string]string{
		"channel": name,
		"token":   apiToken,
	}
	listURL := makeURL(apiURL, "channels.list", qsp)
	resp, err := http.Get(listURL)
	if err != nil {
		return emptyChannel, err
	}
	defer resp.Body.Close()

	var cl channelListResponse
	err = json.NewDecoder(resp.Body).Decode(&cl)
	if err != nil {
		return emptyChannel, err
	}

	if cl.Ok != true {
		return emptyChannel, errors.New("failed to get channel list from Slack API")
	}

	for _, ch := range cl.Channels {
		if ch.Name == name {
			return ch, nil
		}
	}

	return emptyChannel, fmt.Errorf("no channel with name %q on team %q", name, team)
}

func (ch Channel) String() string {
	return fmt.Sprintf("Channel{Id: %s, Name: %s, Members: %v}", ch.Id, ch.Name, ch.Members)
}

// TODO: should this be pointer or value?
func (ch *Channel) UpdateMembers() error {
	qsp := map[string]string{
		"channel": ch.Id,
		"token":   apiToken,
	}
	channelURL := makeURL(apiURL, "channel.info", qsp)

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

	cr := channelResponse{}
	err = json.NewDecoder(resp.Body).Decode(&cr)
	if err != nil {
		return err
	}

	if !cr.Ok {
		return fmt.Errorf("Slack API returned error: %s", cr.Err)
	}

	ch.Members = cr.Channel.Members
	return nil
}

func makeURL(slackURL, method string, qsp map[string]string) string {
	qs := queryString(qsp)
	return fmt.Sprintf(apiURL, team, method, qs)
}

func queryString(qsp map[string]string) string {
	vals := url.Values{}
	for k, v := range qsp {
		vals.Add(k, v)
	}
	return vals.Encode()
}
<commit_msg>Fix wrong method name<commit_after>// TODO: write package comment
package channel

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
)

const (
	team   = "monkeytacos"
	apiURL = "https://%s.slack.com/api/%s?%s"
)

var apiToken string

type channelListResponse struct {
	Channels []Channel `json:"channels"`
	Ok       bool      `json:"ok"`
	Err      string    `json:"error,omitempty"`
}

type channelResponse struct {
	Ok      bool    `json:"ok"`
	Channel Channel `json:"channel"`
	Err     string  `json:"error,omitempty"`
}

type Channel struct {
	Id      string   `json:"id"`
	Name    string   `json:"name"`
	Members []string `json:"members"`
}

func init() {
	apiToken = os.Getenv("SLACK_API_TOKEN")
	if apiToken == "" {
		log.Fatal("SLACK_API_TOKEN not set")
	}
}

func New(name string) (Channel, error) {
	var emptyChannel Channel

	qsp := map[string]string{
		"channel": name,
		"token":   apiToken,
	}
	listURL := makeURL(apiURL, "channels.list", qsp)
	resp, err := http.Get(listURL)
	if err != nil {
		return emptyChannel, err
	}
	defer resp.Body.Close()

	var cl channelListResponse
	err = json.NewDecoder(resp.Body).Decode(&cl)
	if err != nil {
		return emptyChannel, err
	}

	if cl.Ok != true {
		return emptyChannel, errors.New("failed to get channel list from Slack API")
	}

	for _, ch := range cl.Channels {
		if ch.Name == name {
			return ch, nil
		}
	}

	return emptyChannel, fmt.Errorf("no channel with name %q on team %q", name, team)
}

func (ch Channel) String() string {
	return fmt.Sprintf("Channel{Id: %s, Name: %s, Members: %v}", ch.Id, ch.Name, ch.Members)
}

// TODO: should this be pointer or value?
func (ch *Channel) UpdateMembers() error {
	qsp := map[string]string{
		"channel": ch.Id,
		"token":   apiToken,
	}
	channelURL := makeURL(apiURL, "channels.info", qsp)

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

	cr := channelResponse{}
	err = json.NewDecoder(resp.Body).Decode(&cr)
	if err != nil {
		return err
	}

	if !cr.Ok {
		return fmt.Errorf("Slack API returned error: %s", cr.Err)
	}

	ch.Members = cr.Channel.Members
	return nil
}

func makeURL(slackURL, method string, qsp map[string]string) string {
	qs := queryString(qsp)
	return fmt.Sprintf(apiURL, team, method, qs)
}

func queryString(qsp map[string]string) string {
	vals := url.Values{}
	for k, v := range qsp {
		vals.Add(k, v)
	}
	return vals.Encode()
}
<|endoftext|>


<commit_before>// Copyright 2015 The Go Authors. All rights reserved.

// This file is copied from golang.org/x/mobile/internal/mobileinit/ctx_android.go
// and editted.
// This file is licensed under the 3-clause BSD license.

package jni

/*
#include <jni.h>
#include <stdlib.h>

// These definitions are duplicated with those in ctx_android.go of golang.org/x/mobile/internal/mobileinit package.
// To be exact, this might cause undefined behavior, but some compilers including GCC work as a common extension.
// (J.5.11 Multiple external definitions)
JavaVM* current_vm;
jobject current_ctx;

static char* lockJNI(uintptr_t* envp, int* attachedp) {
	JNIEnv* env;

	if (current_vm == NULL) {
		return "no current JVM";
	}

	*attachedp = 0;
	switch ((*current_vm)->GetEnv(current_vm, (void**)&env, JNI_VERSION_1_6)) {
	case JNI_OK:
		break;
	case JNI_EDETACHED:
		if ((*current_vm)->AttachCurrentThread(current_vm, &env, 0) != 0) {
			return "cannot attach to JVM";
		}
		*attachedp = 1;
		break;
	case JNI_EVERSION:
		return "bad JNI version";
	default:
		return "unknown JNI error from GetEnv";
	}

	*envp = (uintptr_t)env;
	return NULL;
}

static char* checkException(uintptr_t jnienv) {
	jthrowable exc;
	JNIEnv* env = (JNIEnv*)jnienv;

	if (!(*env)->ExceptionCheck(env)) {
		return NULL;
	}

	exc = (*env)->ExceptionOccurred(env);
	(*env)->ExceptionClear(env);

	jclass clazz = (*env)->FindClass(env, "java/lang/Throwable");
	jmethodID toString = (*env)->GetMethodID(env, clazz, "toString", "()Ljava/lang/String;");
	jobject msgStr = (*env)->CallObjectMethod(env, exc, toString);
	return (char*)(*env)->GetStringUTFChars(env, msgStr, 0);
}

static void unlockJNI() {
	(*current_vm)->DetachCurrentThread(current_vm);
}
*/
import "C"

import (
	"errors"
	"runtime"
	"unsafe"
)

func RunOnJVM(fn func(vm, env, ctx uintptr) error) error {
	errch := make(chan error)
	go func() {
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		env := C.uintptr_t(0)
		attached := C.int(0)
		if errStr := C.lockJNI(&env, &attached); errStr != nil {
			errch <- errors.New(C.GoString(errStr))
			return
		}
		if attached != 0 {
			defer C.unlockJNI()
		}

		vm := uintptr(unsafe.Pointer(C.current_vm))
		if err := fn(vm, uintptr(env), uintptr(C.current_ctx)); err != nil {
			errch <- err
			return
		}

		if exc := C.checkException(env); exc != nil {
			errch <- errors.New(C.GoString(exc))
			C.free(unsafe.Pointer(exc))
			return
		}
		errch <- nil
	}()
	return <-errch
}
<commit_msg>jni: Add more comment<commit_after>// Copyright 2015 The Go Authors. All rights reserved.

// This file is copied from golang.org/x/mobile/internal/mobileinit/ctx_android.go
// and editted.
// This file is licensed under the 3-clause BSD license.

package jni

/*
#include <jni.h>
#include <stdlib.h>

// These definitions are duplicated with those in ctx_android.go of golang.org/x/mobile/internal/mobileinit package.
// To be exact, this might cause undefined behavior, but some compilers including GCC and Clang work as a common extension.
// (J.5.11 Multiple external definitions)
JavaVM* current_vm;
jobject current_ctx;

static char* lockJNI(uintptr_t* envp, int* attachedp) {
	JNIEnv* env;

	if (current_vm == NULL) {
		return "no current JVM";
	}

	*attachedp = 0;
	switch ((*current_vm)->GetEnv(current_vm, (void**)&env, JNI_VERSION_1_6)) {
	case JNI_OK:
		break;
	case JNI_EDETACHED:
		if ((*current_vm)->AttachCurrentThread(current_vm, &env, 0) != 0) {
			return "cannot attach to JVM";
		}
		*attachedp = 1;
		break;
	case JNI_EVERSION:
		return "bad JNI version";
	default:
		return "unknown JNI error from GetEnv";
	}

	*envp = (uintptr_t)env;
	return NULL;
}

static char* checkException(uintptr_t jnienv) {
	jthrowable exc;
	JNIEnv* env = (JNIEnv*)jnienv;

	if (!(*env)->ExceptionCheck(env)) {
		return NULL;
	}

	exc = (*env)->ExceptionOccurred(env);
	(*env)->ExceptionClear(env);

	jclass clazz = (*env)->FindClass(env, "java/lang/Throwable");
	jmethodID toString = (*env)->GetMethodID(env, clazz, "toString", "()Ljava/lang/String;");
	jobject msgStr = (*env)->CallObjectMethod(env, exc, toString);
	return (char*)(*env)->GetStringUTFChars(env, msgStr, 0);
}

static void unlockJNI() {
	(*current_vm)->DetachCurrentThread(current_vm);
}
*/
import "C"

import (
	"errors"
	"runtime"
	"unsafe"
)

// RunOnJVM executes fn on the current VM context.
//
// RunOnJVM should not be called on init function since the current VM might not be initialized yet.
func RunOnJVM(fn func(vm, env, ctx uintptr) error) error {
	errch := make(chan error)
	go func() {
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		env := C.uintptr_t(0)
		attached := C.int(0)
		if errStr := C.lockJNI(&env, &attached); errStr != nil {
			errch <- errors.New(C.GoString(errStr))
			return
		}
		if attached != 0 {
			defer C.unlockJNI()
		}

		vm := uintptr(unsafe.Pointer(C.current_vm))
		if err := fn(vm, uintptr(env), uintptr(C.current_ctx)); err != nil {
			errch <- err
			return
		}

		if exc := C.checkException(env); exc != nil {
			errch <- errors.New(C.GoString(exc))
			C.free(unsafe.Pointer(exc))
			return
		}
		errch <- nil
	}()
	return <-errch
}
<|endoftext|>