proc_test.go 15.6 KB
Newer Older
D
Derek Parker 已提交
1
package proc
D
Derek Parker 已提交
2 3

import (
4
	"bytes"
5
	"encoding/binary"
6
	"fmt"
D
Derek Parker 已提交
7 8
	"net"
	"net/http"
D
Derek Parker 已提交
9
	"os"
10
	"path/filepath"
11
	"runtime"
12
	"strings"
D
Derek Parker 已提交
13
	"testing"
D
Derek Parker 已提交
14
	"time"
D
Dan Mace 已提交
15

D
Derek Parker 已提交
16
	protest "github.com/derekparker/delve/proc/test"
D
Derek Parker 已提交
17
)
18

19
func init() {
20 21
	runtime.GOMAXPROCS(4)
	os.Setenv("GOMAXPROCS", "4")
22 23
}

D
Dan Mace 已提交
24
func TestMain(m *testing.M) {
D
Derek Parker 已提交
25
	os.Exit(protest.RunTestsWithFixtures(m))
D
Dan Mace 已提交
26 27
}

D
Derek Parker 已提交
28
func withTestProcess(name string, t *testing.T, fn func(p *Process, fixture protest.Fixture)) {
29
	fixture := protest.BuildFixture(name)
D
Dan Mace 已提交
30
	p, err := Launch([]string{fixture.Path})
31 32 33 34
	if err != nil {
		t.Fatal("Launch():", err)
	}

35 36
	defer func() {
		p.Halt()
37
		p.Kill()
38
	}()
39

D
Dan Mace 已提交
40
	fn(p, fixture)
41 42
}

D
Derek Parker 已提交
43
func getRegisters(p *Process, t *testing.T) Registers {
44 45 46 47 48 49 50 51
	regs, err := p.Registers()
	if err != nil {
		t.Fatal("Registers():", err)
	}

	return regs
}

D
Derek Parker 已提交
52
func dataAtAddr(thread *Thread, addr uint64) ([]byte, error) {
53
	data := make([]byte, 1)
D
Derek Parker 已提交
54
	_, err := readMemory(thread, uintptr(addr), data)
55 56 57 58 59 60 61
	if err != nil {
		return nil, err
	}

	return data, nil
}

62 63
func assertNoError(err error, t *testing.T, s string) {
	if err != nil {
64 65
		_, file, line, _ := runtime.Caller(1)
		fname := filepath.Base(file)
66
		t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err)
67 68 69
	}
}

D
Derek Parker 已提交
70
func currentPC(p *Process, t *testing.T) uint64 {
D
Derek Parker 已提交
71
	pc, err := p.PC()
72 73 74 75 76 77 78
	if err != nil {
		t.Fatal(err)
	}

	return pc
}

D
Derek Parker 已提交
79
func currentLineNumber(p *Process, t *testing.T) (string, int) {
80
	pc := currentPC(p, t)
81
	f, l, _ := p.goSymTable.PCToLine(pc)
82

D
Derek Parker 已提交
83
	return f, l
84 85
}

86
func TestExit(t *testing.T) {
D
Derek Parker 已提交
87
	withTestProcess("continuetestprog", t, func(p *Process, fixture protest.Fixture) {
88 89 90
		err := p.Continue()
		pe, ok := err.(ProcessExitedError)
		if !ok {
91
			t.Fatalf("Continue() returned unexpected error type %s", err)
92 93 94 95 96 97 98 99 100 101
		}
		if pe.Status != 0 {
			t.Errorf("Unexpected error status: %d", pe.Status)
		}
		if pe.Pid != p.Pid {
			t.Errorf("Unexpected process id: %d", pe.Pid)
		}
	})
}

D
Derek Parker 已提交
102
func TestHalt(t *testing.T) {
103 104 105 106 107 108 109 110 111 112 113 114
	stopChan := make(chan interface{})
	withTestProcess("loopprog", t, func(p *Process, fixture protest.Fixture) {
		_, err := p.SetBreakpointByLocation("main.loop")
		assertNoError(err, t, "SetBreakpoint")
		assertNoError(p.Continue(), t, "Continue")
		for _, th := range p.Threads {
			if th.running != false {
				t.Fatal("expected running = false for thread", th.Id)
			}
			_, err := th.Registers()
			assertNoError(err, t, "Registers")
		}
D
Derek Parker 已提交
115
		go func() {
D
Dan Mace 已提交
116 117
			for {
				if p.Running() {
118
					if err := p.RequestManualStop(); err != nil {
D
Dan Mace 已提交
119 120
						t.Fatal(err)
					}
121
					stopChan <- nil
D
Dan Mace 已提交
122 123
					return
				}
D
Derek Parker 已提交
124 125
			}
		}()
126 127
		assertNoError(p.Continue(), t, "Continue")
		<-stopChan
D
Derek Parker 已提交
128 129 130 131
		// Loop through threads and make sure they are all
		// actually stopped, err will not be nil if the process
		// is still running.
		for _, th := range p.Threads {
132 133
			if th.running != false {
				t.Fatal("expected running = false for thread", th.Id)
D
Derek Parker 已提交
134
			}
135 136
			_, err := th.Registers()
			assertNoError(err, t, "Registers")
D
Derek Parker 已提交
137 138 139 140
		}
	})
}

141
func TestStep(t *testing.T) {
D
Derek Parker 已提交
142
	withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) {
143
		helloworldfunc := p.goSymTable.LookupFunc("main.helloworld")
144 145
		helloworldaddr := helloworldfunc.Entry

146 147
		_, err := p.SetBreakpoint(helloworldaddr)
		assertNoError(err, t, "SetBreakpoint()")
148 149
		assertNoError(p.Continue(), t, "Continue()")

150
		regs := getRegisters(p, t)
151
		rip := regs.PC()
152

153
		err = p.Step()
D
Derek Parker 已提交
154
		assertNoError(err, t, "Step()")
155

156
		regs = getRegisters(p, t)
157 158 159 160 161
		if rip >= regs.PC() {
			t.Errorf("Expected %#v to be greater than %#v", regs.PC(), rip)
		}
	})
}
162

D
Derek Parker 已提交
163
func TestBreakpoint(t *testing.T) {
D
Derek Parker 已提交
164
	withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) {
165
		helloworldfunc := p.goSymTable.LookupFunc("main.helloworld")
D
Derek Parker 已提交
166
		helloworldaddr := helloworldfunc.Entry
167

168 169
		bp, err := p.SetBreakpoint(helloworldaddr)
		assertNoError(err, t, "SetBreakpoint()")
D
Derek Parker 已提交
170
		assertNoError(p.Continue(), t, "Continue()")
171

D
Derek Parker 已提交
172
		pc, err := p.PC()
173 174 175
		if err != nil {
			t.Fatal(err)
		}
176

D
Derek Parker 已提交
177
		if pc-1 != bp.Addr && pc != bp.Addr {
178
			f, l, _ := p.goSymTable.PCToLine(pc)
D
Derek Parker 已提交
179
			t.Fatalf("Break not respected:\nPC:%#v %s:%d\nFN:%#v \n", pc, f, l, bp.Addr)
180 181
		}
	})
182
}
183

D
Derek Parker 已提交
184
func TestBreakpointInSeperateGoRoutine(t *testing.T) {
D
Derek Parker 已提交
185
	withTestProcess("testthreads", t, func(p *Process, fixture protest.Fixture) {
186
		fn := p.goSymTable.LookupFunc("main.anotherthread")
187 188 189 190
		if fn == nil {
			t.Fatal("No fn exists")
		}

191
		_, err := p.SetBreakpoint(fn.Entry)
192 193 194 195 196 197 198 199 200
		if err != nil {
			t.Fatal(err)
		}

		err = p.Continue()
		if err != nil {
			t.Fatal(err)
		}

D
Derek Parker 已提交
201
		pc, err := p.PC()
202 203 204 205
		if err != nil {
			t.Fatal(err)
		}

206
		f, l, _ := p.goSymTable.PCToLine(pc)
207 208 209 210 211 212
		if f != "testthreads.go" && l != 8 {
			t.Fatal("Program did not hit breakpoint")
		}
	})
}

D
Derek Parker 已提交
213
func TestBreakpointWithNonExistantFunction(t *testing.T) {
D
Derek Parker 已提交
214
	withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) {
215
		_, err := p.SetBreakpoint(0)
216 217 218 219
		if err == nil {
			t.Fatal("Should not be able to break at non existant function")
		}
	})
220
}
221

222
func TestClearBreakpointBreakpoint(t *testing.T) {
D
Derek Parker 已提交
223
	withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) {
224
		fn := p.goSymTable.LookupFunc("main.sleepytime")
225 226
		bp, err := p.SetBreakpoint(fn.Entry)
		assertNoError(err, t, "SetBreakpoint()")
227

228 229
		bp, err = p.ClearBreakpoint(fn.Entry)
		assertNoError(err, t, "ClearBreakpoint()")
230

D
Derek Parker 已提交
231
		data, err := dataAtAddr(p.CurrentThread, bp.Addr)
232 233 234 235
		if err != nil {
			t.Fatal(err)
		}

236
		int3 := []byte{0xcc}
237 238 239 240
		if bytes.Equal(data, int3) {
			t.Fatalf("Breakpoint was not cleared data: %#v, int3: %#v", data, int3)
		}

D
Derek Parker 已提交
241
		if len(p.Breakpoints) != 0 {
242 243 244
			t.Fatal("Breakpoint not removed internally")
		}
	})
245
}
246

247 248 249
type nextTest struct {
	begin, end int
}
250

251
func testnext(program string, testcases []nextTest, initialLocation string, t *testing.T) {
D
Derek Parker 已提交
252
	withTestProcess(program, t, func(p *Process, fixture protest.Fixture) {
253 254
		bp, err := p.SetBreakpointByLocation(initialLocation)
		assertNoError(err, t, "SetBreakpoint()")
255
		assertNoError(p.Continue(), t, "Continue()")
256
		p.ClearBreakpoint(bp.Addr)
257
		p.CurrentThread.SetPC(bp.Addr)
258

D
Derek Parker 已提交
259
		f, ln := currentLineNumber(p, t)
260 261
		for _, tc := range testcases {
			if ln != tc.begin {
D
Derek Parker 已提交
262
				t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln)
263 264 265 266
			}

			assertNoError(p.Next(), t, "Next() returned an error")

D
Derek Parker 已提交
267
			f, ln = currentLineNumber(p, t)
268
			if ln != tc.end {
D
Derek Parker 已提交
269
				t.Fatalf("Program did not continue to correct next location expected %d was %s:%d", tc.end, filepath.Base(f), ln)
270 271
			}
		}
272

D
Derek Parker 已提交
273 274
		if len(p.Breakpoints) != 0 {
			t.Fatal("Not all breakpoints were cleaned up", len(p.Breakpoints))
275
		}
276 277
	})
}
278

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
func TestNextGeneral(t *testing.T) {
	testcases := []nextTest{
		{19, 20},
		{20, 23},
		{23, 24},
		{24, 26},
		{26, 31},
		{31, 23},
		{23, 24},
		{24, 26},
		{26, 31},
		{31, 23},
		{23, 24},
		{24, 26},
		{26, 27},
		{27, 34},
	}
296
	testnext("testnextprog", testcases, "main.testnext", t)
297 298 299 300 301 302
}

func TestNextGoroutine(t *testing.T) {
	testcases := []nextTest{
		{47, 42},
	}
303
	testnext("testnextprog", testcases, "main.testgoroutine", t)
304 305 306 307 308 309
}

func TestNextFunctionReturn(t *testing.T) {
	testcases := []nextTest{
		{14, 35},
	}
310 311 312 313 314 315 316 317 318
	testnext("testnextprog", testcases, "main.helloworld", t)
}

func TestNextFunctionReturnDefer(t *testing.T) {
	testcases := []nextTest{
		{5, 9},
		{9, 6},
	}
	testnext("testnextdefer", testcases, "main.main", t)
319 320
}

D
Derek Parker 已提交
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
func TestNextNetHTTP(t *testing.T) {
	testcases := []nextTest{
		{11, 12},
		{12, 13},
	}
	withTestProcess("testnextnethttp", t, func(p *Process, fixture protest.Fixture) {
		go func() {
			for !p.Running() {
				time.Sleep(50 * time.Millisecond)
			}
			// Wait for program to start listening.
			for {
				conn, err := net.Dial("tcp", ":8080")
				if err == nil {
					conn.Close()
					break
				}
				time.Sleep(50 * time.Millisecond)
			}
			http.Get("http://localhost:8080")
		}()
		if err := p.Continue(); err != nil {
			t.Fatal(err)
		}
		f, ln := currentLineNumber(p, t)
		for _, tc := range testcases {
			if ln != tc.begin {
				t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln)
			}

			assertNoError(p.Next(), t, "Next() returned an error")

			f, ln = currentLineNumber(p, t)
			if ln != tc.end {
				t.Fatalf("Program did not continue to correct next location expected %d was %s:%d", tc.end, filepath.Base(f), ln)
			}
		}
	})
}

D
Derek Parker 已提交
361
func TestRuntimeBreakpoint(t *testing.T) {
D
Derek Parker 已提交
362
	withTestProcess("testruntimebreakpoint", t, func(p *Process, fixture protest.Fixture) {
D
Derek Parker 已提交
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
		err := p.Continue()
		if err != nil {
			t.Fatal(err)
		}
		pc, err := p.PC()
		if err != nil {
			t.Fatal(err)
		}
		_, l, _ := p.PCToLine(pc)
		if l != 10 {
			t.Fatal("did not respect breakpoint")
		}
	})
}

378
func TestFindReturnAddress(t *testing.T) {
D
Derek Parker 已提交
379
	withTestProcess("testnextprog", t, func(p *Process, fixture protest.Fixture) {
380
		var (
381 382
			fdes = p.frameEntries
			gsd  = p.goSymTable
383 384
		)

D
Dan Mace 已提交
385
		start, _, err := gsd.LineToPC(fixture.Source, 24)
386 387 388 389
		if err != nil {
			t.Fatal(err)
		}

390
		_, err = p.SetBreakpoint(start)
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
		if err != nil {
			t.Fatal(err)
		}

		err = p.Continue()
		if err != nil {
			t.Fatal(err)
		}

		regs, err := p.Registers()
		if err != nil {
			t.Fatal(err)
		}

		fde, err := fdes.FDEForPC(start)
		if err != nil {
			t.Fatal(err)
		}

		ret := fde.ReturnAddressOffset(start)
		if err != nil {
			t.Fatal(err)
		}

		addr := uint64(int64(regs.SP()) + ret)
		data := make([]byte, 8)

D
Derek Parker 已提交
418
		readMemory(p.CurrentThread, uintptr(addr), data)
419 420
		addr = binary.LittleEndian.Uint64(data)

421 422 423
		_, l, _ := p.goSymTable.PCToLine(addr)
		if l != 40 {
			t.Fatalf("return address not found correctly, expected line 40")
424 425 426
		}
	})
}
D
Derek Parker 已提交
427 428

func TestSwitchThread(t *testing.T) {
D
Derek Parker 已提交
429
	withTestProcess("testnextprog", t, func(p *Process, fixture protest.Fixture) {
D
Derek Parker 已提交
430 431 432 433 434 435 436 437 438
		// With invalid thread id
		err := p.SwitchThread(-1)
		if err == nil {
			t.Fatal("Expected error for invalid thread id")
		}
		pc, err := p.FindLocation("main.main")
		if err != nil {
			t.Fatal(err)
		}
439
		_, err = p.SetBreakpoint(pc)
D
Derek Parker 已提交
440 441 442 443 444 445 446 447 448
		if err != nil {
			t.Fatal(err)
		}
		err = p.Continue()
		if err != nil {
			t.Fatal(err)
		}
		var nt int
		ct := p.CurrentThread.Id
D
Dan Mace 已提交
449
		for tid := range p.Threads {
D
Derek Parker 已提交
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
			if tid != ct {
				nt = tid
				break
			}
		}
		if nt == 0 {
			t.Fatal("could not find thread to switch to")
		}
		// With valid thread id
		err = p.SwitchThread(nt)
		if err != nil {
			t.Fatal(err)
		}
		if p.CurrentThread.Id != nt {
			t.Fatal("Did not switch threads")
		}
	})
}
A
aarzilli 已提交
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485

type loc struct {
	line int
	fn   string
}

func (l1 *loc) match(l2 Location) bool {
	if l1.line >= 0 {
		if l1.line != l2.Line-1 {
			return false
		}
	}

	return l1.fn == l2.Fn.Name
}

func TestStacktrace(t *testing.T) {
	stacks := [][]loc{
D
Derek Parker 已提交
486 487
		[]loc{{3, "main.stacktraceme"}, {8, "main.func1"}, {16, "main.main"}},
		[]loc{{3, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}},
A
aarzilli 已提交
488
	}
D
Derek Parker 已提交
489
	withTestProcess("stacktraceprog", t, func(p *Process, fixture protest.Fixture) {
490
		bp, err := p.SetBreakpointByLocation("main.stacktraceme")
A
aarzilli 已提交
491 492 493 494
		assertNoError(err, t, "BreakByLocation()")

		for i := range stacks {
			assertNoError(p.Continue(), t, "Continue()")
D
Derek Parker 已提交
495
			locations, err := p.CurrentThread.Stacktrace(40)
A
aarzilli 已提交
496 497 498 499 500 501 502 503 504 505 506 507 508
			assertNoError(err, t, "Stacktrace()")

			if len(locations) != len(stacks[i])+2 {
				t.Fatalf("Wrong stack trace size %d %d\n", len(locations), len(stacks[i])+2)
			}

			for j := range stacks[i] {
				if !stacks[i][j].match(locations[j]) {
					t.Fatalf("Wrong stack trace pos %d\n", j)
				}
			}
		}

509
		p.ClearBreakpoint(bp.Addr)
A
aarzilli 已提交
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
		p.Continue()
	})
}

func stackMatch(stack []loc, locations []Location) bool {
	if len(stack) > len(locations) {
		return false
	}
	for i := range stack {
		if !stack[i].match(locations[i]) {
			return false
		}
	}
	return true
}

func TestStacktraceGoroutine(t *testing.T) {
D
Derek Parker 已提交
527 528
	mainStack := []loc{{11, "main.stacktraceme"}, {21, "main.main"}}
	agoroutineStack := []loc{{-1, "runtime.gopark"}, {-1, "runtime.goparkunlock"}, {-1, "runtime.chansend"}, {-1, "runtime.chansend1"}, {8, "main.agoroutine"}}
A
aarzilli 已提交
529

D
Derek Parker 已提交
530
	withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) {
531
		bp, err := p.SetBreakpointByLocation("main.stacktraceme")
A
aarzilli 已提交
532 533 534 535 536 537 538 539 540 541
		assertNoError(err, t, "BreakByLocation()")

		assertNoError(p.Continue(), t, "Continue()")

		gs, err := p.GoroutinesInfo()
		assertNoError(err, t, "GoroutinesInfo")

		agoroutineCount := 0
		mainCount := 0

D
Derek Parker 已提交
542 543
		for i, g := range gs {
			locations, err := p.GoroutineStacktrace(g, 40)
A
aarzilli 已提交
544 545 546 547 548 549 550 551 552
			assertNoError(err, t, "GoroutineStacktrace()")

			if stackMatch(mainStack, locations) {
				mainCount++
			}

			if stackMatch(agoroutineStack, locations) {
				agoroutineCount++
			} else {
D
Derek Parker 已提交
553
				t.Logf("Non-goroutine stack: %d (%d)", i, len(locations))
A
aarzilli 已提交
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
				for i := range locations {
					name := ""
					if locations[i].Fn != nil {
						name = locations[i].Fn.Name
					}
					t.Logf("\t%s:%d %s\n", locations[i].File, locations[i].Line, name)
				}
			}
		}

		if mainCount != 1 {
			t.Fatalf("Main goroutine stack not found")
		}

		if agoroutineCount != 10 {
			t.Fatalf("Goroutine stacks not found (%d)", agoroutineCount)
		}

572
		p.ClearBreakpoint(bp.Addr)
A
aarzilli 已提交
573 574 575
		p.Continue()
	})
}
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592

func TestKill(t *testing.T) {
	withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) {
		if err := p.Kill(); err != nil {
			t.Fatal(err)
		}
		if p.Exited() != true {
			t.Fatal("expected process to have exited")
		}
		if runtime.GOOS == "linux" {
			_, err := os.Open(fmt.Sprintf("/proc/%d/", p.Pid))
			if err == nil {
				t.Fatal("process has not exited", p.Pid)
			}
		}
	})
}
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616

func testGSupportFunc(name string, t *testing.T, p *Process, fixture protest.Fixture) {
	bp, err := p.SetBreakpointByLocation("main.main")
	assertNoError(err, t, name+": BreakByLocation()")

	assertNoError(p.Continue(), t, name+": Continue()")

	g, err := p.CurrentThread.GetG()
	assertNoError(err, t, name+": GetG()")

	if g == nil {
		t.Fatal(name + ": g was nil")
	}

	t.Logf(name+": g is: %v", g)

	p.ClearBreakpoint(bp.Addr)
}

func TestGetG(t *testing.T) {
	withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) {
		testGSupportFunc("nocgo", t, p, fixture)
	})

617 618 619 620 621
	// On OSX with Go < 1.5 CGO is not supported due to: https://github.com/golang/go/issues/8973
	if runtime.GOOS == "darwin" && strings.Contains(runtime.Version(), "1.4") {
		return
	}

622 623 624 625
	withTestProcess("cgotest", t, func(p *Process, fixture protest.Fixture) {
		testGSupportFunc("cgo", t, p, fixture)
	})
}
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661

func TestContinueMulti(t *testing.T) {
	withTestProcess("integrationprog", t, func(p *Process, fixture protest.Fixture) {
		bp1, err := p.SetBreakpointByLocation("main.main")
		assertNoError(err, t, "BreakByLocation()")

		bp2, err := p.SetBreakpointByLocation("main.sayhi")
		assertNoError(err, t, "BreakByLocation()")

		mainCount := 0
		sayhiCount := 0
		for {
			err := p.Continue()
			if p.exited {
				break
			}
			assertNoError(err, t, "Continue()")

			if p.CurrentBreakpoint().ID == bp1.ID {
				mainCount++
			}

			if p.CurrentBreakpoint().ID == bp2.ID {
				sayhiCount++
			}
		}

		if mainCount != 1 {
			t.Fatalf("Main breakpoint hit wrong number of times: %d\n", mainCount)
		}

		if sayhiCount != 3 {
			t.Fatalf("Sayhi breakpoint hit wrong number of times: %d\n", sayhiCount)
		}
	})
}
662

663
func versionAfterOrEqual(t *testing.T, verStr string, ver GoVersion) {
664 665 666 667
	pver, ok := parseVersionString(verStr)
	if !ok {
		t.Fatalf("Could not parse version string <%s>", verStr)
	}
668
	if !pver.AfterOrEqual(ver) {
669 670 671 672 673 674
		t.Fatalf("Version <%s> parsed as %v not after %v", verStr, pver, ver)
	}
	t.Logf("version string <%s> → %v", verStr, ver)
}

func TestParseVersionString(t *testing.T) {
675 676 677
	versionAfterOrEqual(t, "go1.5.0", GoVersion{1, 5, 0, 0})
	versionAfterOrEqual(t, "go1.4.2", GoVersion{1, 4, 2, 0})
	versionAfterOrEqual(t, "go1.5beta2", GoVersion{1, 5, -1, 2})
678 679 680 681 682 683 684 685
	ver, ok := parseVersionString("devel +17efbfc Tue Jul 28 17:39:19 2015 +0000 linux/amd64")
	if !ok {
		t.Fatalf("Could not parse devel version string")
	}
	if !ver.IsDevel() {
		t.Fatalf("Devel version string not correctly recognized")
	}
}