[libs/logdog] Small tweaks and add fileno to BasicStream.

This allows basic logdog streams to operate as filehandles to subprocess.

Also fixes a bug in normalize and improves an exception message.

R=hinoka@chromium.org, martiniss@chromium.org

Bug: 909848
Change-Id: I4d34a910d83db51123f700b837141157d8dbe39c
Reviewed-on: https://chromium-review.googlesource.com/c/infra/luci/luci-py/+/1512953
Commit-Queue: Robbie Iannucci <iannucci@chromium.org>
Reviewed-by: Ryan Tseng <hinoka@chromium.org>
NOKEYCHECK=True
GitOrigin-RevId: 932668421dd720a45b863f997091938ababdd387
diff --git a/bootstrap.py b/bootstrap.py
index 1207823..ea75822 100644
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -5,7 +5,7 @@
 import collections
 import os
 
-from libs.logdog import stream, streamname
+from . import stream, streamname
 
 
 class NotBootstrappedError(RuntimeError):
diff --git a/stream.py b/stream.py
index 76f3c33..7e02772 100644
--- a/stream.py
+++ b/stream.py
@@ -12,7 +12,7 @@
 import threading
 import types
 
-from libs.logdog import streamname, varint
+from . import streamname, varint
 
 
 _StreamParamsBase = collections.namedtuple('_StreamParamsBase',
@@ -192,6 +192,9 @@
     def fd(self):
       return self._fd
 
+    def fileno(self):
+      return self._fd.fileno()
+
     def write(self, data):
       return self._fd.write(data)
 
@@ -547,6 +550,9 @@
     def __init__(self, fd):
       self._fd = fd
 
+    def fileno(self):
+      return self._fd
+
     def write(self, data):
       self._fd.send(data)
 
diff --git a/streamname.py b/streamname.py
index 5cd3b31..de5a284 100644
--- a/streamname.py
+++ b/streamname.py
@@ -33,7 +33,7 @@
   if len(v) > maxlen:
     raise ValueError('Maximum length exceeded (%d > %d)' % (len(v), maxlen))
   if _STREAM_NAME_RE.match(v) is None:
-    raise ValueError('Invalid stream name')
+    raise ValueError('Invalid stream name: %r' % v)
 
 
 def validate_tag(key, value):
@@ -71,7 +71,9 @@
   else:
     out = []
     for i, ch in enumerate(v):
-      if i == 0 and not _is_valid_stream_char(ch, first=True):
+      # Either the first character in v, or immediately after /
+      isFirst = i == 0 or out[-1][-1] == '/'
+      if isFirst and not _is_valid_stream_char(ch, first=True):
         # The first letter is special, and must be alphanumeric.
         # If we have a prefix, prepend that to the resulting string.
         if prefix is None: