Add string-valued variables to exvar.

R=r
APPROVED=r
DELTA=62  (58 added, 1 deleted, 3 changed)
OCL=27756
CL=27877
diff --git a/src/lib/exvar.go b/src/lib/exvar.go
index d96e40a..a5d91f3 100644
--- a/src/lib/exvar.go
+++ b/src/lib/exvar.go
@@ -19,6 +19,7 @@
 	reservedPrefix = "x-";
 	mismatchedInt = reservedPrefix + "mismatched-int";
 	mismatchedMap = reservedPrefix + "mismatched-map";
+	mismatchedStr = reservedPrefix + "mismatched-str";
 )
 
 // exVar is an abstract type for all exported variables.
@@ -44,8 +45,14 @@
 	return s
 }
 
+// strVar is a string variable, and satisfies the exVar interface.
+type strVar string;
+
+func (s strVar) String() string {
+	return fmt.Sprintf("%q", s)
+}
+
 // TODO(dsymonds):
-// - string-valued vars
 // - dynamic lookup vars (via chan?)
 
 type exVars struct {
@@ -119,6 +126,22 @@
 	return &m
 }
 
+// getOrInitStrVar either gets or initializes a strVar called name.
+func (state *exVars) getOrInitStrVar(name string) *strVar {
+	if v, ok := state.vars[name]; ok {
+		// Existing var
+		if mv, ok := v.(*strVar); ok {
+			return mv
+		}
+		// Type mismatch.
+		return state.getOrInitStrVar(mismatchedStr)
+	}
+	// New var
+	sv := new(strVar);
+	state.vars[name] = sv;
+	return sv
+}
+
 // IncrementInt adds inc to the integer-valued var called name.
 func IncrementInt(name string, inc int) {
 	workSync(func(state *exVars) {
@@ -152,6 +175,13 @@
 	})
 }
 
+// SetStr sets the string-valued var called name to value.
+func SetStr(name string, value string) {
+	workSync(func(state *exVars) {
+		*state.getOrInitStrVar(name) = value
+	})
+}
+
 // GetInt retrieves an integer-valued var called name.
 func GetInt(name string) int {
 	var i int;
@@ -171,6 +201,15 @@
 	return i
 }
 
+// GetStr retrieves a string-valued var called name.
+func GetStr(name string) string {
+	var s string;
+	workSync(func(state *exVars) {
+		s = *state.getOrInitStrVar(name)
+	});
+	return s
+}
+
 // String produces a string of all the vars in textual format.
 func String() string {
 	s := "";
diff --git a/src/lib/exvar_test.go b/src/lib/exvar_test.go
index 8e9b123..89a470a 100644
--- a/src/lib/exvar_test.go
+++ b/src/lib/exvar_test.go
@@ -14,14 +14,14 @@
 	// Unknown exvar should be zero.
 	x := GetInt("requests");
 	if x != 0 {
-		t.Errorf("Get(nonexistent) = %v, want 0", x)
+		t.Errorf("GetInt(nonexistent) = %v, want 0", x)
 	}
 
 	IncrementInt("requests", 1);
 	IncrementInt("requests", 3);
 	x = GetInt("requests");
 	if x != 4 {
-		t.Errorf("Get('requests') = %v, want 4", x)
+		t.Errorf("GetInt('requests') = %v, want 4", x)
 	}
 
 	out := String();
@@ -31,10 +31,23 @@
 	}
 }
 
+func TestStringVar(t *testing.T) {
+	// Unknown exvar should be empty string.
+	if s := GetStr("name"); s != "" {
+		t.Errorf("GetStr(nonexistent) = %q, want ''", s)
+	}
+
+	SetStr("name", "Mike");
+	if s := GetStr("name"); s != "Mike" {
+		t.Errorf("GetStr('name') = %q, want 'Mike'", s)
+	}
+}
+
 func TestMismatchedCounters(t *testing.T) {
 	// Make sure some vars exist.
 	GetInt("requests");
 	GetMapInt("colours", "red");
+	GetStr("name");
 
 	IncrementInt("colours", 1);
 	if x := GetInt("x-mismatched-int"); x != 1 {
@@ -43,7 +56,12 @@
 
 	IncrementMapInt("requests", "orange", 1);
 	if x := GetMapInt("x-mismatched-map", "orange"); x != 1 {
-		t.Errorf("GetMapInt('x-mismatched-int', 'orange') = %v, want 1", x)
+		t.Errorf("GetMapInt('x-mismatched-map', 'orange') = %v, want 1", x)
+	}
+
+	SetStr("requests", "apple");
+	if s := GetStr("x-mismatched-str"); s != "apple" {
+		t.Errorf("GetStr('x-mismatched-str') = %q, want 'apple'", s)
 	}
 }