From e63974e17721a2b6f448592e8ff9b31483e7238d Mon Sep 17 00:00:00 2001 From: Angela Li Date: Mon, 19 Jun 2017 18:21:45 -0700 Subject: [PATCH 01/16] Implemented trace library based on the autogen, gax only --- trace/google/cloud/trace/__init__.py | 23 +++ trace/google/cloud/trace/_gax.py | 259 +++++++++++++++++++++++++ trace/google/cloud/trace/client.py | 205 +++++++++++++++++++ trace/google/cloud/trace/trace.py | 121 ++++++++++++ trace/google/cloud/trace/trace_span.py | 130 +++++++++++++ 5 files changed, 738 insertions(+) create mode 100644 trace/google/cloud/trace/__init__.py create mode 100644 trace/google/cloud/trace/_gax.py create mode 100644 trace/google/cloud/trace/client.py create mode 100644 trace/google/cloud/trace/trace.py create mode 100644 trace/google/cloud/trace/trace_span.py diff --git a/trace/google/cloud/trace/__init__.py b/trace/google/cloud/trace/__init__.py new file mode 100644 index 000000000000..64be5a215f61 --- /dev/null +++ b/trace/google/cloud/trace/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2017 Google 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. + +from pkg_resources import get_distribution +__version__ = get_distribution('google-cloud-trace').version + +ASCENDING = 'timestamp asc' +"""Query string to order by ascending timestamps.""" +DESCENDING = 'timestamp desc' +"""Query string to order by decending timestamps.""" + +__all__ = ['__version__', 'ASCENDING', 'Client', 'DESCENDING'] diff --git a/trace/google/cloud/trace/_gax.py b/trace/google/cloud/trace/_gax.py new file mode 100644 index 000000000000..5f6c7b18fba6 --- /dev/null +++ b/trace/google/cloud/trace/_gax.py @@ -0,0 +1,259 @@ +# Copyright 2017 Google 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. + +"""GAX Wrapper for interacting with the Stackdriver Trace API.""" + +from google.cloud.gapic.trace.v1.trace_service_client import ( + TraceServiceClient) +from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( + TraceSpan, Trace, Traces) + +from google.cloud._helpers import make_secure_channel +from google.cloud._http import DEFAULT_USER_AGENT + + +class _TraceAPI(object): + """Wrapper to help mapping trace-related APIs. + + See + https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. + cloudtrace.v1 + + :type gax_api: + :class:`~google.cloud.gapic.trace.v1.trace_service_client.TraceServiceClient` + :param gax_api: API object used to make GAX requests. + + :type client: :class:`~google.cloud.trace.client.Client` + :param client: The client that owns this API object. + """ + def __init__(self, gax_api, client): + self._gax_api = gax_api + self.client = client + + def patch_traces(self, project_id, traces, options=None): + """Sends new traces to Stackdriver Trace or updates existing traces. + + :type project_id: str + :param project_id: ID of the Cloud project where the trace data is stored. + + :type traces: dict + :param traces: The traces to be patched in the API call. + + :type options: :class:`~google.gax.CallOptions` + :param options: (Optional) Overrides the default settings for this call, + e.g, timeout, retries etc. + """ + traces_pb = _trace_mapping_to_pb(traces) + self._gax_api.patch_traces(project_id, traces_pb, options) + + def get_trace(self, project_id, trace_id, options=None): + """Gets a single trace by its ID. + + :type project_id: str + :param project_id: ID of the Cloud project where the trace data is stored. + + :type trace_id: str + :param trace_id: ID of the trace to return. + + :type options: :class:`~google.gax.CallOptions` + :param options: (Optional) Overrides the default settings for this call, + e.g, timeout, retries etc. + + :rtype: :dict + :returns: A Trace dict. + """ + trace_pb = self._gax_api.get_trace(project_id, trace_id, options) + trace_mapping = _trace_pb_to_mapping(trace_pb) + return trace_mapping + + def list_traces( + self, + project_id, + view=None, + page_size=None, + start_time=None, + end_time=None, + filter_=None, + order_by=None, + options=None): + """Returns of a list of traces that match the specified filter conditions. + + :type project_id: str + :param project_id: ID of the Cloud project where the trace data is stored. + + :type view: :class:`google.cloud.gapic.trace.v1.enums.ListTracesRequest.ViewType` + :param view: (Optional) Type of data returned for traces in the list. + Default is ``MINIMAL``. + + :type page_size: int + :param page_size: (Optional) Maximum number of traces to return. If not specified + or <= 0, the implementation selects a reasonable value. + The implementation may return fewer traces than the requested + page size. + + :type start_time: :class:`google.protobuf.timestamp_pb2.Timestamp` + :param start_time: (Optional) Start of the time interval (inclusive) during which + the trace data was collected from the application. + + :type end_time: :class:`google.protobuf.timestamp_pb2.Timestamp` + :param end_time: (Optional) End of the time interval (inclusive) during which + the trace data was collected from the application. + + :type filter: str + :param filter_: (Optional) An optional filter for the request. + + :type order_by: str + :param order_by: (Optional) Field used to sort the returned traces. + + :type options: :class:`google.gax.CallOptions` + :param options: Overrides the default settings for this call. + e.g, timeout, retries etc. + + :rtype: dict + :returns: Traces that match the specified filter conditions. + """ + page_iter = self._gax_api.list_traces( + project_id, + view, + page_size, + start_time, + end_time, + filter_, + order_by, + options) + traces = [_trace_pb_to_mapping(trace_pb) + for trace_pb in page_iter] + return traces + + +def _trace_pb_to_mapping(trace_pb): + """Convert a trace protobuf to dict. + + :type trace_pb: class:`google.cloud.proto.devtools.cloudtrace.v1.trace_pb2.Trace` + :param trace_pb: A trace protobuf instance. + + :rtype: dict + :return: The converted trace dict. + """ + mapping = { + 'project_id': trace_pb.project_id, + 'trace_id': trace_pb.trace_id, + } + + spans = [] + + for span_pb in trace_pb.spans: + span = { + 'span_id': span_pb.span_id, + 'kind': span_pb.kind, + 'name': span_pb.name, + 'parent_span_id': span_pb.parent_span_id, + } + + # Convert the Timestamp protobuf to dict. + time_keys = ['start_time', 'end_time'] + + for time_key in time_keys: + time_pb = getattr(span_pb, time_key) + time_mapping = { + 'seconds': time_pb.seconds, + 'nanos': time_pb.nanos, + } + span[time_key] = time_mapping + + labels = {} + + for key, value in span_pb.labels.items(): + labels[key] = value + + span['labels'] = labels + spans.append(span) + + mapping['spans'] = spans + + return mapping + + +def _trace_mapping_to_pb(mapping): + """Convert a trace dict to protobuf. + + :type mapping: dict + :param mapping: A trace mapping. + + :rtype: class:`google.cloud.proto.devtools.cloudtrace.v1.trace_pb2.Trace` + :return: The converted protobuf type trace. + """ + # Mapping from the key name in dict to key name in protobuf in a span. + span_scalar_keys = { + 'spanId': 'span_id', + 'kind': 'kind', + 'name': 'name', + 'parentSpanId': 'parent_span_id', + 'labels': 'labels', + } + + # Mapping from the key name in dict to key name in protobuf in a trace. + trace_scalar_keys = { + 'traceId': 'trace_id', + 'projectId': 'project_id', + } + + traces = [] + + for trace in mapping['traces']: + spans = [] + + # Build the protobuf for TraceSpan. + # Protobuf type attributes cannot be set by setattr. + for span in trace['spans']: + span_pb = TraceSpan(start_time=span['startTime'], end_time=span['endTime']) + + for key, pb_name in span_scalar_keys.items(): + + if key in span: + setattr(span_pb, pb_name, span[key]) + + spans.append(span_pb) + + # Build the protobuf for Trace. + trace_pb = Trace(spans=spans) + + for key, pb_name in trace_scalar_keys.items(): + + if key in trace: + setattr(trace_pb, pb_name, trace[key]) + + traces.append(trace_pb) + + # Build the protobuf for Traces. + traces_pb = Traces(traces=traces) + + return traces_pb + + +def make_gax_trace_api(client): + """Create an instance of the GAX Trace API. + + :type client: :class:`~google.cloud.trace.client.Client` + :param client: The client that holds configuration details. + + :rtype: :class:`~google.cloud.trace._gax._TraceAPI` + :return: A Trace API instance with the proper configurations. + """ + channel = make_secure_channel( + client._credentials, + DEFAULT_USER_AGENT, + TraceServiceClient.SERVICE_ADDRESS) + generated = TraceServiceClient(channel=channel, lib_name='gccl') + return _TraceAPI(generated, client) diff --git a/trace/google/cloud/trace/client.py b/trace/google/cloud/trace/client.py new file mode 100644 index 000000000000..9da6b036640b --- /dev/null +++ b/trace/google/cloud/trace/client.py @@ -0,0 +1,205 @@ +# Copyright 2017 Google 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. + +"""Client for interacting with the Stackdriver Trace API.""" + +from google.cloud.trace._gax import make_gax_trace_api +from google.cloud.trace.trace import Trace +from google.cloud.client import ClientWithProject +from google.protobuf.timestamp_pb2 import Timestamp + +from datetime import datetime +import math + + +class Client(ClientWithProject): + """Client to bundle configuration needed for API requests. + + :type project: str + :param project: The project which the client acts on behalf of. + If not passed, falls back to the default inferred from + the environment. + + :type credentials: :class:`~google.auth.credentials.Credentials` + :param credentials: (Optional) The OAuth2 Credentials to use for this client. + If not passed, falls back to the default inferred from + the environment. + """ + _trace_api = None + + def __init__(self, project=None, credentials=None): + super(Client, self).__init__( + project=project, credentials=credentials) + + @property + def trace_api(self): + """Helper for trace-related API calls. + + See + https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. + cloudtrace.v1 + """ + self._trace_api = make_gax_trace_api(self) + return self._trace_api + + def trace(self, project_id=None, trace_id=None): + """Initialize a new trace instance. + + :type project_id: str + :param project_id: ID of the Cloud project where the trace data is stored. + + :type trace_id: str + :param trace_id: ID of the trace. 32 digits uuid. + + :rtype: :class:`~google.cloud.trace.trace.Trace` + :return: A Trace instance. + """ + if project_id is None: + project_id = self.project + return Trace(client=self, project_id=project_id, trace_id=trace_id) + + def patch_traces(self, traces, project_id=None, options=None): + """Sends new traces to Stackdriver Trace or updates existing traces. + + :type traces: dict + :param traces: The traces to be patched in the API call. + + :type project_id: str + :param project_id: (Optional) ID of the Cloud project where the trace data is stored. + + :type options: :class:`~google.gax.CallOptions` + :param options: (Optional) Overrides the default settings for this call, + e.g, timeout, retries etc. + """ + if project_id is None: + project_id = self.project + + return self.trace_api.patch_traces( + project_id=project_id, + traces=traces, + options=options) + + def get_trace(self, trace_id, project_id=None, options=None): + """Gets a single trace by its ID. + + :type project_id: str + :param project_id: ID of the Cloud project where the trace data is stored. + + :type trace_id: str + :param trace_id: ID of the trace to return. + + :type options: :class:`~google.gax.CallOptions` + :param options: (Optional) Overrides the default settings for this call, + e.g, timeout, retries etc. + + :rtype: :dict + :returns: A Trace dict. + """ + if project_id is None: + project_id = self.project + + return self.trace_api.get_trace( + project_id=project_id, + trace_id=trace_id, + options=options) + + def list_traces( + self, + project_id=None, + view=None, + page_size=None, + start_time=None, + end_time=None, + filter_=None, + order_by=None, + options=None): + """Returns of a list of traces that match the specified filter conditions. + + :type project_id: str + :param project_id: (Optional) ID of the Cloud project where the trace data is stored. + + :type view: :class:`google.cloud.gapic.trace.v1.enums.ListTracesRequest.ViewType` + :param view: (Optional) Type of data returned for traces in the list. + Default is ``MINIMAL``. + + :type page_size: int + :param page_size: (Optional) Maximum number of traces to return. If not specified + or <= 0, the implementation selects a reasonable value. + The implementation may return fewer traces than the requested + page size. + + :type start_time: :class:`~datetime.datetime` + :param start_time: (Optional) Start of the time interval (inclusive) during which + the trace data was collected from the application. + + :type end_time: :class:`~datetime.datetime` + :param end_time: (Optional) End of the time interval (inclusive) during which + the trace data was collected from the application. + + :type filter: str + :param filter_: (Optional) An optional filter for the request. + + :type order_by: str + :param order_by: (Optional) Field used to sort the returned traces. + + :type options: :class:`google.gax.CallOptions` + :param options: Overrides the default settings for this call. + e.g, timeout, retries etc. + + :rtype: dict + :returns: Traces that match the specified filter conditions. + """ + if project_id is None: + project_id = self.project + + if start_time is not None: + start_time = _datetime_to_timestamp_protobuf(start_time) + + if end_time is not None: + end_time = _datetime_to_timestamp_protobuf(end_time) + + return self.trace_api.list_traces( + project_id=project_id, + view=view, + page_size=page_size, + start_time=start_time, + end_time=end_time, + filter_=filter_, + order_by=order_by, + options=options) + + +def _datetime_to_timestamp_protobuf(datetime_str): + """Helper to convert datetime to Timestamp protobuf. + + :type datetime_str: str + :param datetime: Datetime string to be converted. + Format required: %Y-%m-%dT%H:%M:%S.%f + Use datetime.isoformat() to convert to this format. + e.g. datetime.now().isoformat() + + :rtype: :class:`~google.protobuf.timestamp_pb2.Timestamp` + :return: A Timestamp protobuf instance. + """ + datetime_parsed = datetime.strptime(datetime_str, '%Y-%m-%dT%H:%M:%S.%f') + timestamp = datetime_parsed.timestamp() + parts = divmod(timestamp, 1) + seconds = int(parts[0]) + nanos = round(parts[1] * math.pow(10, 9)) + + timestamp_pb = Timestamp() + timestamp_pb.seconds = seconds + timestamp_pb.nanos = nanos + + return timestamp_pb diff --git a/trace/google/cloud/trace/trace.py b/trace/google/cloud/trace/trace.py new file mode 100644 index 000000000000..8b8b27707d75 --- /dev/null +++ b/trace/google/cloud/trace/trace.py @@ -0,0 +1,121 @@ +# Copyright 2017 Google 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. + +"""Trace for interacting with the Stackdriver Trace API.""" + +from google.cloud.trace.trace_span import TraceSpan +from google.cloud.trace.trace_span import format_span_json +from subprocess import check_output + + +class Trace(object): + """A trace describes how long it takes for an application to perform + an operation. It consists of a set of spans, each of which represent + a single timed event within the operation. + + See + https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. + cloudtrace.v1#google.devtools.cloudtrace.v1.Trace + + :type client: :class:`~google.cloud.trace.client.Client` + :param client: A client which holds the credentials and project configuration + for the trace. + + :type project_id: str + :param project_id: (Optional) The project_id for the trace. + + :type trace_id: str + :param trace_id: (Optional) Trace_id is a 32 digits uuid for the trace. + If not given, will generate one automatically. + """ + def __init__(self, client, project_id=None, trace_id=None): + self.client = client + + if project_id is None: + project_id = client.project + + self.project_id = project_id + + if trace_id is None: + trace_id = generate_trace_id() + + self.trace_id = trace_id + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.finish() + + def start(self): + """Start a trace, initialize an empty list of spans.""" + self.spans = [] + + def finish(self): + """Send the trace to Stackdriver Trace API and clear the spans.""" + self.send() + self.spans = [] + + def span(self, name='span'): + """Create a new span for the trace and append it to the spans list. + + :type name: str + :param name: The name of the span. + + :rtype: :class: `~google.cloud.trace.trace_span.TraceSpan` + :returns: A TraceSpan to be added to the current Trace. + """ + span = TraceSpan(name) + self.spans.append(span) + return span + + def send(self): + """API call: Patch trace to Stackdriver Trace. + + See + https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. + cloudtrace.v1#google.devtools.cloudtrace.v1.TraceService.PatchTraces + """ + spans = [format_span_json(span) for span in self.spans] + + if len(spans) == 0: + return + + trace = { + 'projectId': self.project_id, + 'traceId': self.trace_id, + 'spans': spans, + } + + traces = { + 'traces': [trace], + } + + self.client.patch_traces( + project_id=self.project_id, + traces=traces, + options=None) + + +def generate_trace_id(): + """Generate a trace_id randomly. + + :rtype: str + :return: 32 digits randomly generated trace ID. + """ + trace_id = check_output('uuidgen | sed s/-//g', shell=True)\ + .decode('utf-8')\ + .rstrip('\n') + return trace_id diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py new file mode 100644 index 000000000000..f20dc63a0a56 --- /dev/null +++ b/trace/google/cloud/trace/trace_span.py @@ -0,0 +1,130 @@ +# Copyright 2017 Google 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. + +"""TraceSpan for sending traces to the Stackdriver Trace API.""" + +from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum +from google.protobuf.timestamp_pb2 import Timestamp + +import random + + +class TraceSpan(object): + """A span is an individual timed event which forms a node of the trace tree. + Each span has its name, span id and parent id. The parent id indicates the + causal relationships between the individual spans in a single distributed trace. + Span that does not have a parent id is called root span. All spans associated + with a specific trace also share a common trace id. Spans do not need to be + continuous, there can be gaps between two spans. + + See + https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools.cloudtrace.v1 + #google.devtools.cloudtrace.v1.TraceSpan + + :type name: str + :param name: The name of the span. + + :type kind: :class:`~google.cloud.trace.span.SpanKind` + :param kind: Distinguishes between spans generated in a particular context. + For example, two spans with the same name may be distinguished using + RPC_CLIENT and RPC_SERVER to identify queueing latency associated + with the span. + + :type parent_span_id: str + :param parent_span_id: ID of the parent span. Optional. + + :type labels: dict + :param labels: Collection of labels associated with the span. + Label keys must be less than 128 bytes. + Label values must be less than 16 kilobytes. + + :type span_id: str + :param span_id: Identifier for the span, unique within a trace. + """ + + def __init__( + self, + name, + kind=Enum.SpanKind.SPAN_KIND_UNSPECIFIED, + parent_span_id=None, + labels=None, + span_id=None): + self.name = name + self.kind = kind + self.parent_span_id = parent_span_id + self.labels = labels + + if span_id is None: + self.span_id = self.generate_span_id() + + def set_start_time(self): + """Set the start time for a span.""" + timestamp = Timestamp() + timestamp.GetCurrentTime() + self.start_time = { + 'seconds': timestamp.seconds, + 'nanos': timestamp.nanos, + } + + def set_end_time(self): + """Set the end time for a span.""" + timestamp = Timestamp() + timestamp.GetCurrentTime() + self.end_time = { + 'seconds': timestamp.seconds, + 'nanos': timestamp.nanos, + } + + def __enter__(self): + self.set_start_time() + return self + + def __exit__(self, exception_type, exception_value, traceback): + self.set_end_time() + + def generate_span_id(self): + """Return the random generated span ID for a span. + + :rtype: int + :returns: Identifier for the span. Must be a 64-bit integer other than 0 and + unique within a trace. Converted to string. + """ + span_id = str(random.getrandbits(64)) + return int(span_id) + + +def format_span_json(span): + """Helper to format a TraceSpan in JSON format. + + :type span: :class:`~google.cloud.trace.trace_span.TraceSpan` + :param span: A TraceSpan to be transferred to JSON format. + + :rtype: dict + :return: Formatted TraceSpan. + """ + span_json = { + 'name': span.name, + 'kind': span.kind, + 'spanId': span.span_id, + 'startTime': span.start_time, + 'endTime': span.end_time, + } + + if span.parent_span_id is not None: + span_json['parentSpanId'] = span.parent_span_id + + if span.labels is not None: + span_json['labels'] = span.labels + + return span_json From 661c463f9f4e2564c6009d321bcf06e9f7362e12 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Tue, 20 Jun 2017 15:29:06 -0700 Subject: [PATCH 02/16] Address Jon's comments, add support for nested spans. --- trace/google/cloud/trace/__init__.py | 12 +++---- trace/google/cloud/trace/client.py | 11 ++++-- trace/google/cloud/trace/trace.py | 46 ++++++++++++++++++++++---- trace/google/cloud/trace/trace_span.py | 29 +++++++++++++++- trace/setup.py | 2 +- 5 files changed, 82 insertions(+), 18 deletions(-) diff --git a/trace/google/cloud/trace/__init__.py b/trace/google/cloud/trace/__init__.py index 64be5a215f61..134ba8e00baf 100644 --- a/trace/google/cloud/trace/__init__.py +++ b/trace/google/cloud/trace/__init__.py @@ -12,12 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pkg_resources import get_distribution -__version__ = get_distribution('google-cloud-trace').version +from google.cloud.trace._gax import _TraceAPI +from google.cloud.trace.client import Client +from google.cloud.trace.trace import Trace +from google.cloud.trace.trace_span import TraceSpan -ASCENDING = 'timestamp asc' -"""Query string to order by ascending timestamps.""" -DESCENDING = 'timestamp desc' -"""Query string to order by decending timestamps.""" -__all__ = ['__version__', 'ASCENDING', 'Client', 'DESCENDING'] +__all__ = ['_TraceAPI', 'Client', 'Trace', 'TraceSpan'] diff --git a/trace/google/cloud/trace/client.py b/trace/google/cloud/trace/client.py index 9da6b036640b..4e8341a18b2b 100644 --- a/trace/google/cloud/trace/client.py +++ b/trace/google/cloud/trace/client.py @@ -22,6 +22,8 @@ from datetime import datetime import math +_DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%f' + class Client(ClientWithProject): """Client to bundle configuration needed for API requests. @@ -190,9 +192,14 @@ def _datetime_to_timestamp_protobuf(datetime_str): e.g. datetime.now().isoformat() :rtype: :class:`~google.protobuf.timestamp_pb2.Timestamp` - :return: A Timestamp protobuf instance. + :returns: A Timestamp protobuf instance. """ - datetime_parsed = datetime.strptime(datetime_str, '%Y-%m-%dT%H:%M:%S.%f') + try: + datetime_parsed = datetime.strptime(datetime_str, _DATETIME_FORMAT) + except ValueError: + return 'Time data {} does not match the format {}.'\ + .format(datetime_str, _DATETIME_FORMAT) + timestamp = datetime_parsed.timestamp() parts = divmod(timestamp, 1) seconds = int(parts[0]) diff --git a/trace/google/cloud/trace/trace.py b/trace/google/cloud/trace/trace.py index 8b8b27707d75..76ccc89773a9 100644 --- a/trace/google/cloud/trace/trace.py +++ b/trace/google/cloud/trace/trace.py @@ -18,6 +18,8 @@ from google.cloud.trace.trace_span import format_span_json from subprocess import check_output +_GENERATE_TRACE_ID_COMMAND = 'uuidgen | sed s/-//g' + class Trace(object): """A trace describes how long it takes for an application to perform @@ -30,7 +32,7 @@ class Trace(object): :type client: :class:`~google.cloud.trace.client.Client` :param client: A client which holds the credentials and project configuration - for the trace. + for the trace. :type project_id: str :param project_id: (Optional) The project_id for the trace. @@ -74,7 +76,7 @@ def span(self, name='span'): :type name: str :param name: The name of the span. - :rtype: :class: `~google.cloud.trace.trace_span.TraceSpan` + :rtype: :class:`~google.cloud.trace.trace_span.TraceSpan` :returns: A TraceSpan to be added to the current Trace. """ span = TraceSpan(name) @@ -88,15 +90,18 @@ def send(self): https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. cloudtrace.v1#google.devtools.cloudtrace.v1.TraceService.PatchTraces """ - spans = [format_span_json(span) for span in self.spans] + spans_list = [] + for root_span in self.spans: + span_tree = traverse_span_tree(root_span) + spans_list.extend(span_tree) - if len(spans) == 0: + if len(spans_list) == 0: return trace = { 'projectId': self.project_id, 'traceId': self.trace_id, - 'spans': spans, + 'spans': spans_list, } traces = { @@ -109,13 +114,40 @@ def send(self): options=None) +def traverse_span_tree(root_span): + """Helper to traverse the span tree in level order. + + :rtype: :class:`~google.cloud.trace.trace_span.TraceSpan` + :param root_span: The root span in a span tree. + + :rtype: list + :returns: A list of all the spans in a span tree. + """ + span_list = [] + + if root_span is None: + return span_list + + span_queue = [] + span_queue.append(root_span) + + while span_queue: + cur_span = span_queue.pop(0) + span_list.append(format_span_json(cur_span)) + + for child_span in cur_span.child_spans: + span_queue.append(child_span) + + return span_list + + def generate_trace_id(): """Generate a trace_id randomly. :rtype: str - :return: 32 digits randomly generated trace ID. + :returns: 32 digits randomly generated trace ID. """ - trace_id = check_output('uuidgen | sed s/-//g', shell=True)\ + trace_id = check_output(_GENERATE_TRACE_ID_COMMAND, shell=True)\ .decode('utf-8')\ .rstrip('\n') return trace_id diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index f20dc63a0a56..b2828fb3bfd2 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -49,6 +49,14 @@ class TraceSpan(object): Label keys must be less than 128 bytes. Label values must be less than 16 kilobytes. + :type start_time: :class:`~datetime.datetime` + :param start_time: (Optional) Start of the time interval (inclusive) during which + the trace data was collected from the application. + + :type end_time: :class:`~datetime.datetime` + :param end_time: (Optional) End of the time interval (inclusive) during which + the trace data was collected from the application. + :type span_id: str :param span_id: Identifier for the span, unique within a trace. """ @@ -59,15 +67,34 @@ def __init__( kind=Enum.SpanKind.SPAN_KIND_UNSPECIFIED, parent_span_id=None, labels=None, + start_time=None, + end_time=None, span_id=None): self.name = name self.kind = kind self.parent_span_id = parent_span_id self.labels = labels + self.start_time = start_time + self.end_time = end_time if span_id is None: self.span_id = self.generate_span_id() + self.child_spans = [] + + def span(self, name='child_span'): + """Create a child span for the current span and append it to the child spans list. + + :type name: str + :param name: (Optional) The name of the child span. + + :rtype: :class: `~google.cloud.trace.trace_span.TraceSpan` + :returns: A child TraceSpan to be added to the current span. + """ + child_span = TraceSpan(name, parent_span_id=self.span_id) + self.child_spans.append(child_span) + return child_span + def set_start_time(self): """Set the start time for a span.""" timestamp = Timestamp() @@ -111,7 +138,7 @@ def format_span_json(span): :param span: A TraceSpan to be transferred to JSON format. :rtype: dict - :return: Formatted TraceSpan. + :returns: Formatted TraceSpan. """ span_json = { 'name': span.name, diff --git a/trace/setup.py b/trace/setup.py index 886e20393f61..aeeae31756e4 100644 --- a/trace/setup.py +++ b/trace/setup.py @@ -6,11 +6,11 @@ """ from setuptools import setup, find_packages -import sys install_requires = [ 'google-gax>=0.15.7, <0.16dev', 'googleapis-common-protos[grpc]>=1.5.2, <2.0dev', + 'google-cloud-core >= 0.24.0, < 0.25dev', ] setup( From f8574b072b7487c77028088b71639bde93868d82 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Tue, 20 Jun 2017 18:12:42 -0700 Subject: [PATCH 03/16] Fix lint and modify nox.py --- trace/google/cloud/trace/_gax.py | 81 +++++++++++++++----------- trace/google/cloud/trace/client.py | 77 +++++++++++++----------- trace/google/cloud/trace/trace.py | 18 +++--- trace/google/cloud/trace/trace_span.py | 43 +++++++------- trace/nox.py | 32 +++++++++- 5 files changed, 150 insertions(+), 101 deletions(-) diff --git a/trace/google/cloud/trace/_gax.py b/trace/google/cloud/trace/_gax.py index 5f6c7b18fba6..f5fff2ec6633 100644 --- a/trace/google/cloud/trace/_gax.py +++ b/trace/google/cloud/trace/_gax.py @@ -25,15 +25,16 @@ class _TraceAPI(object): """Wrapper to help mapping trace-related APIs. - + See https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. cloudtrace.v1 - + :type gax_api: - :class:`~google.cloud.gapic.trace.v1.trace_service_client.TraceServiceClient` + :class:`~google.cloud.gapic.trace.v1.trace_service_client. + TraceServiceClient` :param gax_api: API object used to make GAX requests. - + :type client: :class:`~google.cloud.trace.client.Client` :param client: The client that owns this API object. """ @@ -45,14 +46,15 @@ def patch_traces(self, project_id, traces, options=None): """Sends new traces to Stackdriver Trace or updates existing traces. :type project_id: str - :param project_id: ID of the Cloud project where the trace data is stored. - + :param project_id: ID of the Cloud project where the trace data is + stored. + :type traces: dict :param traces: The traces to be patched in the API call. - + :type options: :class:`~google.gax.CallOptions` - :param options: (Optional) Overrides the default settings for this call, - e.g, timeout, retries etc. + :param options: (Optional) Overrides the default settings for this + call, e.g, timeout, retries etc. """ traces_pb = _trace_mapping_to_pb(traces) self._gax_api.patch_traces(project_id, traces_pb, options) @@ -61,15 +63,16 @@ def get_trace(self, project_id, trace_id, options=None): """Gets a single trace by its ID. :type project_id: str - :param project_id: ID of the Cloud project where the trace data is stored. + :param project_id: ID of the Cloud project where the trace data is + stored. :type trace_id: str :param trace_id: ID of the trace to return. :type options: :class:`~google.gax.CallOptions` - :param options: (Optional) Overrides the default settings for this call, - e.g, timeout, retries etc. - + :param options: (Optional) Overrides the default settings for this + call, e.g, timeout, retries etc. + :rtype: :dict :returns: A Trace dict. """ @@ -87,32 +90,37 @@ def list_traces( filter_=None, order_by=None, options=None): - """Returns of a list of traces that match the specified filter conditions. - + """Returns of a list of traces that match the specified filter + conditions. + :type project_id: str - :param project_id: ID of the Cloud project where the trace data is stored. + :param project_id: ID of the Cloud project where the trace data is + stored. - :type view: :class:`google.cloud.gapic.trace.v1.enums.ListTracesRequest.ViewType` - :param view: (Optional) Type of data returned for traces in the list. + :type view: :class:`google.cloud.gapic.trace.v1.enums. + ListTracesRequest.ViewType` + :param view: (Optional) Type of data returned for traces in the list. Default is ``MINIMAL``. - + :type page_size: int - :param page_size: (Optional) Maximum number of traces to return. If not specified - or <= 0, the implementation selects a reasonable value. - The implementation may return fewer traces than the requested - page size. - + :param page_size: (Optional) Maximum number of traces to return. + If not specified or <= 0, the implementation selects + a reasonable value. The implementation may return + fewer traces than the requested page size. + :type start_time: :class:`google.protobuf.timestamp_pb2.Timestamp` - :param start_time: (Optional) Start of the time interval (inclusive) during which - the trace data was collected from the application. - + :param start_time: (Optional) Start of the time interval (inclusive) + during which the trace data was collected from the + application. + :type end_time: :class:`google.protobuf.timestamp_pb2.Timestamp` - :param end_time: (Optional) End of the time interval (inclusive) during which - the trace data was collected from the application. - + :param end_time: (Optional) End of the time interval (inclusive) + during which the trace data was collected from the + application. + :type filter: str :param filter_: (Optional) An optional filter for the request. - + :type order_by: str :param order_by: (Optional) Field used to sort the returned traces. @@ -139,8 +147,9 @@ def list_traces( def _trace_pb_to_mapping(trace_pb): """Convert a trace protobuf to dict. - - :type trace_pb: class:`google.cloud.proto.devtools.cloudtrace.v1.trace_pb2.Trace` + + :type trace_pb: class:`google.cloud.proto.devtools.cloudtrace.v1. + trace_pb2.Trace` :param trace_pb: A trace protobuf instance. :rtype: dict @@ -187,7 +196,7 @@ def _trace_pb_to_mapping(trace_pb): def _trace_mapping_to_pb(mapping): """Convert a trace dict to protobuf. - + :type mapping: dict :param mapping: A trace mapping. @@ -217,7 +226,9 @@ def _trace_mapping_to_pb(mapping): # Build the protobuf for TraceSpan. # Protobuf type attributes cannot be set by setattr. for span in trace['spans']: - span_pb = TraceSpan(start_time=span['startTime'], end_time=span['endTime']) + span_pb = TraceSpan( + start_time=span['startTime'], + end_time=span['endTime']) for key, pb_name in span_scalar_keys.items(): diff --git a/trace/google/cloud/trace/client.py b/trace/google/cloud/trace/client.py index 4e8341a18b2b..48bcd8b8cb0d 100644 --- a/trace/google/cloud/trace/client.py +++ b/trace/google/cloud/trace/client.py @@ -27,16 +27,16 @@ class Client(ClientWithProject): """Client to bundle configuration needed for API requests. - + :type project: str :param project: The project which the client acts on behalf of. If not passed, falls back to the default inferred from the environment. - + :type credentials: :class:`~google.auth.credentials.Credentials` - :param credentials: (Optional) The OAuth2 Credentials to use for this client. - If not passed, falls back to the default inferred from - the environment. + :param credentials: (Optional) The OAuth2 Credentials to use for this + client. If not passed, falls back to the default + inferred from the environment. """ _trace_api = None @@ -47,7 +47,7 @@ def __init__(self, project=None, credentials=None): @property def trace_api(self): """Helper for trace-related API calls. - + See https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. cloudtrace.v1 @@ -57,9 +57,10 @@ def trace_api(self): def trace(self, project_id=None, trace_id=None): """Initialize a new trace instance. - + :type project_id: str - :param project_id: ID of the Cloud project where the trace data is stored. + :param project_id: ID of the Cloud project where the trace data + is stored. :type trace_id: str :param trace_id: ID of the trace. 32 digits uuid. @@ -73,16 +74,17 @@ def trace(self, project_id=None, trace_id=None): def patch_traces(self, traces, project_id=None, options=None): """Sends new traces to Stackdriver Trace or updates existing traces. - + :type traces: dict :param traces: The traces to be patched in the API call. :type project_id: str - :param project_id: (Optional) ID of the Cloud project where the trace data is stored. - + :param project_id: (Optional) ID of the Cloud project where the trace + data is stored. + :type options: :class:`~google.gax.CallOptions` - :param options: (Optional) Overrides the default settings for this call, - e.g, timeout, retries etc. + :param options: (Optional) Overrides the default settings for this + call, e.g, timeout, retries etc. """ if project_id is None: project_id = self.project @@ -96,15 +98,16 @@ def get_trace(self, trace_id, project_id=None, options=None): """Gets a single trace by its ID. :type project_id: str - :param project_id: ID of the Cloud project where the trace data is stored. + :param project_id: ID of the Cloud project where the trace data is + stored. :type trace_id: str :param trace_id: ID of the trace to return. :type options: :class:`~google.gax.CallOptions` - :param options: (Optional) Overrides the default settings for this call, - e.g, timeout, retries etc. - + :param options: (Optional) Overrides the default settings for this + call, e.g, timeout, retries etc. + :rtype: :dict :returns: A Trace dict. """ @@ -126,32 +129,36 @@ def list_traces( filter_=None, order_by=None, options=None): - """Returns of a list of traces that match the specified filter conditions. - + """Returns of a list of traces that match the filter conditions. + :type project_id: str - :param project_id: (Optional) ID of the Cloud project where the trace data is stored. + :param project_id: (Optional) ID of the Cloud project where the trace + data is stored. - :type view: :class:`google.cloud.gapic.trace.v1.enums.ListTracesRequest.ViewType` - :param view: (Optional) Type of data returned for traces in the list. + :type view: :class:`google.cloud.gapic.trace.v1.enums. + ListTracesRequest.ViewType` + :param view: (Optional) Type of data returned for traces in the list. Default is ``MINIMAL``. - + :type page_size: int - :param page_size: (Optional) Maximum number of traces to return. If not specified - or <= 0, the implementation selects a reasonable value. - The implementation may return fewer traces than the requested - page size. - + :param page_size: (Optional) Maximum number of traces to return. + If not specified or <= 0, the implementation selects + a reasonable value. The implementation may return + fewer traces than the requested page size. + :type start_time: :class:`~datetime.datetime` - :param start_time: (Optional) Start of the time interval (inclusive) during which - the trace data was collected from the application. - + :param start_time: (Optional) Start of the time interval (inclusive) + during which the trace data was collected from the + application. + :type end_time: :class:`~datetime.datetime` - :param end_time: (Optional) End of the time interval (inclusive) during which - the trace data was collected from the application. - + :param end_time: (Optional) End of the time interval (inclusive) during + which the trace data was collected from the + application. + :type filter: str :param filter_: (Optional) An optional filter for the request. - + :type order_by: str :param order_by: (Optional) Field used to sort the returned traces. diff --git a/trace/google/cloud/trace/trace.py b/trace/google/cloud/trace/trace.py index 76ccc89773a9..d6f9a3425efe 100644 --- a/trace/google/cloud/trace/trace.py +++ b/trace/google/cloud/trace/trace.py @@ -25,18 +25,18 @@ class Trace(object): """A trace describes how long it takes for an application to perform an operation. It consists of a set of spans, each of which represent a single timed event within the operation. - + See https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. cloudtrace.v1#google.devtools.cloudtrace.v1.Trace - + :type client: :class:`~google.cloud.trace.client.Client` - :param client: A client which holds the credentials and project configuration - for the trace. - + :param client: A client which holds the credentials and project + configuration for the trace. + :type project_id: str :param project_id: (Optional) The project_id for the trace. - + :type trace_id: str :param trace_id: (Optional) Trace_id is a 32 digits uuid for the trace. If not given, will generate one automatically. @@ -72,10 +72,10 @@ def finish(self): def span(self, name='span'): """Create a new span for the trace and append it to the spans list. - + :type name: str :param name: The name of the span. - + :rtype: :class:`~google.cloud.trace.trace_span.TraceSpan` :returns: A TraceSpan to be added to the current Trace. """ @@ -85,7 +85,7 @@ def span(self, name='span'): def send(self): """API call: Patch trace to Stackdriver Trace. - + See https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. cloudtrace.v1#google.devtools.cloudtrace.v1.TraceService.PatchTraces diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index b2828fb3bfd2..512104852a5b 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -21,25 +21,26 @@ class TraceSpan(object): - """A span is an individual timed event which forms a node of the trace tree. - Each span has its name, span id and parent id. The parent id indicates the - causal relationships between the individual spans in a single distributed trace. - Span that does not have a parent id is called root span. All spans associated - with a specific trace also share a common trace id. Spans do not need to be - continuous, there can be gaps between two spans. + """A span is an individual timed event which forms a node of the trace + tree. Each span has its name, span id and parent id. The parent id + indicates the causal relationships between the individual spans in a + single distributed trace. Span that does not have a parent id is called + root span. All spans associated with a specific trace also share a common + trace id. Spans do not need to be continuous, there can be gaps between + two spans. See - https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools.cloudtrace.v1 - #google.devtools.cloudtrace.v1.TraceSpan + https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. + cloudtrace.v1#google.devtools.cloudtrace.v1.TraceSpan :type name: str :param name: The name of the span. :type kind: :class:`~google.cloud.trace.span.SpanKind` :param kind: Distinguishes between spans generated in a particular context. - For example, two spans with the same name may be distinguished using - RPC_CLIENT and RPC_SERVER to identify queueing latency associated - with the span. + For example, two spans with the same name may be + distinguished using RPC_CLIENT and RPC_SERVER to identify + queueing latency associated with the span. :type parent_span_id: str :param parent_span_id: ID of the parent span. Optional. @@ -50,12 +51,13 @@ class TraceSpan(object): Label values must be less than 16 kilobytes. :type start_time: :class:`~datetime.datetime` - :param start_time: (Optional) Start of the time interval (inclusive) during which - the trace data was collected from the application. + :param start_time: (Optional) Start of the time interval (inclusive) + during which the trace data was collected from the + application. :type end_time: :class:`~datetime.datetime` - :param end_time: (Optional) End of the time interval (inclusive) during which - the trace data was collected from the application. + :param end_time: (Optional) End of the time interval (inclusive) during + which the trace data was collected from the application. :type span_id: str :param span_id: Identifier for the span, unique within a trace. @@ -83,7 +85,8 @@ def __init__( self.child_spans = [] def span(self, name='child_span'): - """Create a child span for the current span and append it to the child spans list. + """Create a child span for the current span and append it to the child + spans list. :type name: str :param name: (Optional) The name of the child span. @@ -124,8 +127,8 @@ def generate_span_id(self): """Return the random generated span ID for a span. :rtype: int - :returns: Identifier for the span. Must be a 64-bit integer other than 0 and - unique within a trace. Converted to string. + :returns: Identifier for the span. Must be a 64-bit integer other + than 0 and unique within a trace. Converted to string. """ span_id = str(random.getrandbits(64)) return int(span_id) @@ -133,10 +136,10 @@ def generate_span_id(self): def format_span_json(span): """Helper to format a TraceSpan in JSON format. - + :type span: :class:`~google.cloud.trace.trace_span.TraceSpan` :param span: A TraceSpan to be transferred to JSON format. - + :rtype: dict :returns: Formatted TraceSpan. """ diff --git a/trace/nox.py b/trace/nox.py index 0f6bd713afbe..647b994358b0 100644 --- a/trace/nox.py +++ b/trace/nox.py @@ -17,6 +17,9 @@ import nox +LOCAL_DEPS = ('../core/',) + + @nox.session @nox.parametrize('python_version', ['2.7', '3.4', '3.5', '3.6']) def unit_tests(session, python_version): @@ -26,11 +29,24 @@ def unit_tests(session, python_version): session.interpreter = 'python{}'.format(python_version) # Install all test dependencies, then install this package in-place. - session.install('mock', 'pytest', 'pytest-cov') + session.install('mock', 'pytest', 'pytest-cov', *LOCAL_DEPS) session.install('-e', '.') # Run py.test against the unit tests. - session.run('py.test', '--quiet', 'tests/') + session.run('py.test', '--quiet', 'tests/unit') + + +@nox.session +def lint(session): + """Run flake8. + Returns a failure if flake8 finds linting errors or sufficiently + serious code quality issues. + """ + session.interpreter = 'python3.6' + session.install('flake8', *LOCAL_DEPS) + session.install('.') + session.run('flake8', 'google/cloud/trace') + @nox.session def lint_setup_py(session): @@ -39,3 +55,15 @@ def lint_setup_py(session): session.install('docutils', 'pygments') session.run( 'python', 'setup.py', 'check', '--restructuredtext', '--strict') + + +@nox.session +def cover(session): + """Run the final coverage report. + This outputs the coverage report aggregating coverage from the unit + test runs (not system test runs), and then erases coverage data. + """ + session.interpreter = 'python3.6' + session.install('coverage', 'pytest-cov') + session.run('coverage', 'report', '--show-missing', '--fail-under=100') + session.run('coverage', 'erase') From 2b091704009ac00243a1ad23e89328b1eb43d876 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Wed, 21 Jun 2017 11:07:18 -0700 Subject: [PATCH 04/16] Use the FromDatetime function to convert datetime to timestamp protobuf --- trace/google/cloud/trace/client.py | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/trace/google/cloud/trace/client.py b/trace/google/cloud/trace/client.py index 48bcd8b8cb0d..8df7f3c5bfcf 100644 --- a/trace/google/cloud/trace/client.py +++ b/trace/google/cloud/trace/client.py @@ -22,8 +22,6 @@ from datetime import datetime import math -_DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%f' - class Client(ClientWithProject): """Client to bundle configuration needed for API requests. @@ -189,31 +187,16 @@ def list_traces( options=options) -def _datetime_to_timestamp_protobuf(datetime_str): +def _datetime_to_timestamp_protobuf(date_time): """Helper to convert datetime to Timestamp protobuf. - :type datetime_str: str - :param datetime: Datetime string to be converted. - Format required: %Y-%m-%dT%H:%M:%S.%f - Use datetime.isoformat() to convert to this format. - e.g. datetime.now().isoformat() + :type date_time: class:`~datetime.datetime` + :param date_time: Datetime to be converted. :rtype: :class:`~google.protobuf.timestamp_pb2.Timestamp` :returns: A Timestamp protobuf instance. """ - try: - datetime_parsed = datetime.strptime(datetime_str, _DATETIME_FORMAT) - except ValueError: - return 'Time data {} does not match the format {}.'\ - .format(datetime_str, _DATETIME_FORMAT) - - timestamp = datetime_parsed.timestamp() - parts = divmod(timestamp, 1) - seconds = int(parts[0]) - nanos = round(parts[1] * math.pow(10, 9)) - timestamp_pb = Timestamp() - timestamp_pb.seconds = seconds - timestamp_pb.nanos = nanos + timestamp_pb.FromDatetime(date_time) return timestamp_pb From 3549f282833f03696c7cc442170b61d4ce92dd4e Mon Sep 17 00:00:00 2001 From: Angela Li Date: Thu, 22 Jun 2017 15:35:21 -0700 Subject: [PATCH 05/16] Add unit test for _gax.py and client.py --- trace/google/cloud/trace/_gax.py | 165 +++-------- trace/google/cloud/trace/_helper.py | 65 +++++ trace/google/cloud/trace/client.py | 35 +-- trace/google/cloud/trace/trace_span.py | 16 +- trace/tests/__init__.py | 13 + trace/tests/unit/test__gax.py | 383 +++++++++++++++++++++++++ trace/tests/unit/test_client.py | 126 ++++++++ 7 files changed, 645 insertions(+), 158 deletions(-) create mode 100644 trace/google/cloud/trace/_helper.py create mode 100644 trace/tests/__init__.py create mode 100644 trace/tests/unit/test__gax.py create mode 100644 trace/tests/unit/test_client.py diff --git a/trace/google/cloud/trace/_gax.py b/trace/google/cloud/trace/_gax.py index f5fff2ec6633..f05e1b11042e 100644 --- a/trace/google/cloud/trace/_gax.py +++ b/trace/google/cloud/trace/_gax.py @@ -16,11 +16,13 @@ from google.cloud.gapic.trace.v1.trace_service_client import ( TraceServiceClient) -from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( - TraceSpan, Trace, Traces) - +from google.cloud.trace._helper import _traces_mapping_to_pb +from google.gax import CallOptions +from google.gax import INITIAL_PAGE from google.cloud._helpers import make_secure_channel from google.cloud._http import DEFAULT_USER_AGENT +from google.cloud.iterator import GAXIterator +from google.protobuf.json_format import MessageToDict class _TraceAPI(object): @@ -56,7 +58,7 @@ def patch_traces(self, project_id, traces, options=None): :param options: (Optional) Overrides the default settings for this call, e.g, timeout, retries etc. """ - traces_pb = _trace_mapping_to_pb(traces) + traces_pb = _traces_mapping_to_pb(traces) self._gax_api.patch_traces(project_id, traces_pb, options) def get_trace(self, project_id, trace_id, options=None): @@ -77,7 +79,7 @@ def get_trace(self, project_id, trace_id, options=None): :returns: A Trace dict. """ trace_pb = self._gax_api.get_trace(project_id, trace_id, options) - trace_mapping = _trace_pb_to_mapping(trace_pb) + trace_mapping = _parse_trace_pb(trace_pb) return trace_mapping def list_traces( @@ -89,7 +91,7 @@ def list_traces( end_time=None, filter_=None, order_by=None, - options=None): + page_token=None): """Returns of a list of traces that match the specified filter conditions. @@ -124,133 +126,58 @@ def list_traces( :type order_by: str :param order_by: (Optional) Field used to sort the returned traces. - :type options: :class:`google.gax.CallOptions` - :param options: Overrides the default settings for this call. - e.g, timeout, retries etc. + :type page_token: str + :param page_token: opaque marker for the next "page" of entries. If not + passed, the API will return the first page of + entries. :rtype: dict :returns: Traces that match the specified filter conditions. """ + if page_token is None: + page_token = INITIAL_PAGE + options = CallOptions(page_token=page_token) page_iter = self._gax_api.list_traces( - project_id, - view, - page_size, - start_time, - end_time, - filter_, - order_by, - options) - traces = [_trace_pb_to_mapping(trace_pb) - for trace_pb in page_iter] - return traces - - -def _trace_pb_to_mapping(trace_pb): - """Convert a trace protobuf to dict. - - :type trace_pb: class:`google.cloud.proto.devtools.cloudtrace.v1. - trace_pb2.Trace` + project_id=project_id, + view=view, + page_size=page_size, + start_time=start_time, + end_time=end_time, + filter_=filter_, + order_by=order_by, + options=options) + item_to_value = _item_to_mapping + return GAXIterator(self.client, page_iter, item_to_value) + + +def _parse_trace_pb(trace_pb): + """Parse a ``Trace`` protobuf to a dictionary. + + :type trace_pb: :class:`google.cloud.proto.devtools.cloudtrace.v1. + trace_pb2.Trace` :param trace_pb: A trace protobuf instance. :rtype: dict - :return: The converted trace dict. + :returns: The converted trace dict. """ - mapping = { - 'project_id': trace_pb.project_id, - 'trace_id': trace_pb.trace_id, - } - - spans = [] - - for span_pb in trace_pb.spans: - span = { - 'span_id': span_pb.span_id, - 'kind': span_pb.kind, - 'name': span_pb.name, - 'parent_span_id': span_pb.parent_span_id, - } - - # Convert the Timestamp protobuf to dict. - time_keys = ['start_time', 'end_time'] - - for time_key in time_keys: - time_pb = getattr(span_pb, time_key) - time_mapping = { - 'seconds': time_pb.seconds, - 'nanos': time_pb.nanos, - } - span[time_key] = time_mapping - - labels = {} + try: + return MessageToDict(trace_pb) + except TypeError: + raise - for key, value in span_pb.labels.items(): - labels[key] = value - span['labels'] = labels - spans.append(span) +def _item_to_mapping(iterator, trace_pb): + """Helper callable function for the GAXIterator - mapping['spans'] = spans + :type iterator: :class:`~google.cloud.iterator.Iterator` + :param iterator: The iterator that is currently in use. - return mapping - - -def _trace_mapping_to_pb(mapping): - """Convert a trace dict to protobuf. - - :type mapping: dict - :param mapping: A trace mapping. - - :rtype: class:`google.cloud.proto.devtools.cloudtrace.v1.trace_pb2.Trace` - :return: The converted protobuf type trace. + :type trace_pb: :class:`google.cloud.proto.devtools.cloudtrace.v1. + trace_pb2.Trace` + :param trace_pb: A trace protobuf instance. """ - # Mapping from the key name in dict to key name in protobuf in a span. - span_scalar_keys = { - 'spanId': 'span_id', - 'kind': 'kind', - 'name': 'name', - 'parentSpanId': 'parent_span_id', - 'labels': 'labels', - } - - # Mapping from the key name in dict to key name in protobuf in a trace. - trace_scalar_keys = { - 'traceId': 'trace_id', - 'projectId': 'project_id', - } - - traces = [] - - for trace in mapping['traces']: - spans = [] - - # Build the protobuf for TraceSpan. - # Protobuf type attributes cannot be set by setattr. - for span in trace['spans']: - span_pb = TraceSpan( - start_time=span['startTime'], - end_time=span['endTime']) - - for key, pb_name in span_scalar_keys.items(): - - if key in span: - setattr(span_pb, pb_name, span[key]) - - spans.append(span_pb) - - # Build the protobuf for Trace. - trace_pb = Trace(spans=spans) - - for key, pb_name in trace_scalar_keys.items(): - - if key in trace: - setattr(trace_pb, pb_name, trace[key]) - - traces.append(trace_pb) - - # Build the protobuf for Traces. - traces_pb = Traces(traces=traces) - - return traces_pb + mapping = _parse_trace_pb(trace_pb) + return mapping def make_gax_trace_api(client): diff --git a/trace/google/cloud/trace/_helper.py b/trace/google/cloud/trace/_helper.py new file mode 100644 index 000000000000..ca32faed1dcc --- /dev/null +++ b/trace/google/cloud/trace/_helper.py @@ -0,0 +1,65 @@ +# Copyright 2017 Google 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. + +from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( + TraceSpan, Trace, Traces) +from google.protobuf.json_format import ParseDict + + +def _trace_mapping_to_pb(trace_mapping): + """Helper for converting trace mapping to pb, + Performs "impedance matching" between the protobuf attrs and + the keys expected in the JSON API. + + :type trace_mapping: dict + :param trace_mapping: The trace dict. + + :rtype: :class: `~google.cloud.proto.devtools.cloudtrace.v1. + trace_pb2.Trace` + :returns: The Trace protobuf instance. + """ + trace_pb = Trace() + ParseDict(trace_mapping, trace_pb) + return trace_pb + + +def _span_mapping_to_pb(span_mapping): + """Helper for converting span mapping to pb, + Performs "impedance matching" between the protobuf attrs and + the keys expected in the JSON API. + + :type span_mapping: dict + :param span_mapping: The span dict. + + :rtype: :class: `~google.cloud.proto.devtools.cloudtrace.v1. + trace_pb2.TraceSpan` + :returns: The TraceSpan protobuf instance. + """ + span_pb = TraceSpan() + ParseDict(span_mapping, span_pb) + return span_pb + + +def _traces_mapping_to_pb(traces_mapping): + """Convert a trace dict to protobuf. + + :type traces_mapping: dict + :param traces_mapping: A trace mapping. + + :rtype: class:`google.cloud.proto.devtools.cloudtrace.v1.trace_pb2.Traces` + :return: The converted protobuf type traces. + """ + traces_pb = Traces() + ParseDict(traces_mapping, traces_pb) + return traces_pb diff --git a/trace/google/cloud/trace/client.py b/trace/google/cloud/trace/client.py index 8df7f3c5bfcf..3f06c0c098f1 100644 --- a/trace/google/cloud/trace/client.py +++ b/trace/google/cloud/trace/client.py @@ -17,10 +17,7 @@ from google.cloud.trace._gax import make_gax_trace_api from google.cloud.trace.trace import Trace from google.cloud.client import ClientWithProject -from google.protobuf.timestamp_pb2 import Timestamp - -from datetime import datetime -import math +from google.cloud._helpers import _datetime_to_pb_timestamp class Client(ClientWithProject): @@ -126,7 +123,7 @@ def list_traces( end_time=None, filter_=None, order_by=None, - options=None): + page_token=None): """Returns of a list of traces that match the filter conditions. :type project_id: str @@ -160,9 +157,10 @@ def list_traces( :type order_by: str :param order_by: (Optional) Field used to sort the returned traces. - :type options: :class:`google.gax.CallOptions` - :param options: Overrides the default settings for this call. - e.g, timeout, retries etc. + :type page_token: str + :param page_token: opaque marker for the next "page" of entries. If not + passed, the API will return the first page of + entries. :rtype: dict :returns: Traces that match the specified filter conditions. @@ -171,10 +169,10 @@ def list_traces( project_id = self.project if start_time is not None: - start_time = _datetime_to_timestamp_protobuf(start_time) + start_time = _datetime_to_pb_timestamp(start_time) if end_time is not None: - end_time = _datetime_to_timestamp_protobuf(end_time) + end_time = _datetime_to_pb_timestamp(end_time) return self.trace_api.list_traces( project_id=project_id, @@ -184,19 +182,4 @@ def list_traces( end_time=end_time, filter_=filter_, order_by=order_by, - options=options) - - -def _datetime_to_timestamp_protobuf(date_time): - """Helper to convert datetime to Timestamp protobuf. - - :type date_time: class:`~datetime.datetime` - :param date_time: Datetime to be converted. - - :rtype: :class:`~google.protobuf.timestamp_pb2.Timestamp` - :returns: A Timestamp protobuf instance. - """ - timestamp_pb = Timestamp() - timestamp_pb.FromDatetime(date_time) - - return timestamp_pb + page_token=page_token) diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index 512104852a5b..ef8366513c2f 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -14,8 +14,8 @@ """TraceSpan for sending traces to the Stackdriver Trace API.""" +from datetime import datetime from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum -from google.protobuf.timestamp_pb2 import Timestamp import random @@ -100,21 +100,11 @@ def span(self, name='child_span'): def set_start_time(self): """Set the start time for a span.""" - timestamp = Timestamp() - timestamp.GetCurrentTime() - self.start_time = { - 'seconds': timestamp.seconds, - 'nanos': timestamp.nanos, - } + self.start_time = datetime.utcnow().isoformat() + 'Z' def set_end_time(self): """Set the end time for a span.""" - timestamp = Timestamp() - timestamp.GetCurrentTime() - self.end_time = { - 'seconds': timestamp.seconds, - 'nanos': timestamp.nanos, - } + self.end_time = datetime.utcnow().isoformat() + 'Z' def __enter__(self): self.set_start_time() diff --git a/trace/tests/__init__.py b/trace/tests/__init__.py new file mode 100644 index 000000000000..0fe161d30fc3 --- /dev/null +++ b/trace/tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2017 Google 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. \ No newline at end of file diff --git a/trace/tests/unit/test__gax.py b/trace/tests/unit/test__gax.py new file mode 100644 index 000000000000..7d6a8adb9680 --- /dev/null +++ b/trace/tests/unit/test__gax.py @@ -0,0 +1,383 @@ +# Copyright 2017 Google 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. + +import unittest + +import mock + +from google.cloud._testing import _GAXBaseAPI + + +def _make_credentials(): + import google.auth.credentials + + return mock.Mock(spec=google.auth.credentials.Credentials) + + +class _Base(object): + PROJECT = 'PROJECT' + PROJECT_PATH = 'projects/%s' % (PROJECT,) + + def _make_one(self, *args, **kw): + return self._get_target_class()(*args, **kw) + + +class Test__TraceAPI(_Base, unittest.TestCase): + + @staticmethod + def _get_target_class(): + from google.cloud.trace._gax import _TraceAPI + + return _TraceAPI + + def test_constructor(self): + gax_api = _GAXTraceAPI() + client = object() + api = self._make_one(gax_api, client) + self.assertIs(api._gax_api, gax_api) + self.assertIs(api.client, client) + + def test_patch_traces(self): + from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( + TraceSpan, Trace, Traces) + from google.cloud._helpers import _datetime_to_pb_timestamp + + from datetime import datetime + + PROJECT = 'PROJECT' + TRACE_ID = 'test_trace_id' + SPAN_ID = 1234 + SPAN_NAME = 'test_span_name' + START_TIME = datetime.utcnow() + END_TIME = datetime.utcnow() + + TRACES = { + 'traces': [ + { + 'projectId': PROJECT, + 'traceId': TRACE_ID, + 'spans': [ + { + 'spanId': SPAN_ID, + 'name': SPAN_NAME, + 'startTime': START_TIME.isoformat() + 'Z', + 'endTime': END_TIME.isoformat() + 'Z', + }, + ], + }, + ], + } + + gax_api = _GAXTraceAPI() + api = self._make_one(gax_api, None) + api.patch_traces(project_id=PROJECT, traces=TRACES) + project_id, traces, options = (gax_api._patch_traces_called_with) + + self.assertEqual(len(traces.traces), 1) + trace = traces.traces[0] + + self.assertEqual(len(trace.spans), 1) + span = trace.spans[0] + + self.assertIsInstance(traces, Traces) + self.assertEqual(trace.project_id, PROJECT) + self.assertEqual(trace.trace_id, TRACE_ID) + self.assertIsInstance(trace, Trace) + + self.assertEqual(span.span_id, SPAN_ID) + self.assertEqual(span.name, SPAN_NAME) + self.assertEqual( + span.start_time, + _datetime_to_pb_timestamp(START_TIME)) + self.assertEqual( + span.end_time, + _datetime_to_pb_timestamp(END_TIME)) + self.assertIsInstance(span, TraceSpan) + + self.assertIsNone(options) + + def test_get_trace(self): + from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( + Trace) + + PROJECT = 'PROJECT' + TRACE_ID = 'test_trace_id' + TRACE_PB = Trace(project_id=PROJECT, trace_id=TRACE_ID) + + gax_api = _GAXTraceAPI(_get_trace_response=TRACE_PB) + api = self._make_one(gax_api, None) + + api.get_trace(project_id=PROJECT, trace_id=TRACE_ID) + + project_id, trace_id, options = gax_api._get_traces_called_with + self.assertEqual(project_id, PROJECT) + self.assertEqual(trace_id, TRACE_ID) + self.assertIsNone(options) + + def _make_trace_pb( + self, + project, + trace_id, + span_id, + span_name, + start_time, + end_time, + parent_span_id, + labels): + from google.cloud.trace._gax import _traces_mapping_to_pb + + SPAN_KIND = 2 + + TRACES = { + 'traces': [ + { + 'projectId': project, + 'traceId': trace_id, + 'spans': [ + { + 'spanId': span_id, + 'name': span_name, + 'startTime': start_time, + 'endTime': end_time, + 'kind': SPAN_KIND, + 'parentSpanId': parent_span_id, + 'labels': labels, + }, + ], + }, + ], + } + + traces_pb = _traces_mapping_to_pb(TRACES) + trace_pb = traces_pb.traces + return trace_pb + + def test_list_traces(self): + from google.cloud._testing import _GAXPageIterator + from google.cloud.gapic.trace.v1.enums import ListTracesRequest as Enum + + from datetime import datetime + + PROJECT = 'PROJECT' + TRACE_ID = 'test_trace_id' + SPAN_ID = 1234 + SPAN_NAME = 'test_span_name' + SPAN_KIND = 'RPC_CLIENT' + PARENT_SPAN_ID = 123 + START_TIME = datetime.utcnow().isoformat() + 'Z' + END_TIME = datetime.utcnow().isoformat() + 'Z' + LABELS = { + '/http/status_code': '200', + '/component': 'HTTP load balancer', + } + + SIZE = 10 + TOKEN = 'TOKEN' + VIEW_TYPE = Enum.ViewType.COMPLETE + + trace_pb = self._make_trace_pb( + PROJECT, + TRACE_ID, + SPAN_ID, + SPAN_NAME, + START_TIME, + END_TIME, + PARENT_SPAN_ID, + LABELS) + + response = _GAXPageIterator(trace_pb) + gax_api = _GAXTraceAPI(_list_traces_response=response) + api = self._make_one(gax_api, None) + + iterator = api.list_traces( + project_id=PROJECT, + view=VIEW_TYPE, + page_size=SIZE, + page_token=TOKEN) + + traces = list(iterator) + + self.assertEqual(len(traces), 1) + trace = traces[0] + + self.assertEqual(len(trace['spans']), 1) + span = trace['spans'][0] + + self.assertEqual(trace['projectId'], PROJECT) + self.assertEqual(trace['traceId'], TRACE_ID) + + self.assertEqual(span['spanId'], str(SPAN_ID)) + self.assertEqual(span['name'], SPAN_NAME) + + self.assertEqual( + span['startTime'], START_TIME) + self.assertEqual( + span['endTime'], END_TIME) + self.assertEqual(span['kind'], SPAN_KIND) + self.assertEqual(span['parentSpanId'], str(PARENT_SPAN_ID)) + self.assertEqual(span['labels'], LABELS) + + project_id, view, page_size, start_time, end_time, filter_, order_by, options = ( + gax_api._list_traces_called_with + ) + self.assertEqual(project_id, PROJECT) + self.assertEqual(view, VIEW_TYPE) + self.assertEqual(page_size, SIZE) + self.assertIsNone(start_time) + self.assertIsNone(end_time) + self.assertIsNone(filter_) + self.assertIsNone(order_by) + self.assertEqual(options.page_token, TOKEN) + + +class Test__parse_trace_pb(unittest.TestCase): + + @staticmethod + def _call_fut(*args, **kwargs): + from google.cloud.trace._gax import _parse_trace_pb + + return _parse_trace_pb(*args, **kwargs) + + def test_registered_type(self): + from google.cloud._helpers import _datetime_to_pb_timestamp + from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( + TraceSpan, Trace) + + from datetime import datetime + + PROJECT = u'PROJECT' + TRACE_ID = u'test_trace_id' + SPAN_ID = 1234 + SPAN_NAME = u'test_span_name' + START_TIME = datetime.utcnow() + END_TIME = datetime.utcnow() + + start_time_pb = _datetime_to_pb_timestamp(START_TIME) + end_time_pb = _datetime_to_pb_timestamp(END_TIME) + + span_pb = TraceSpan( + span_id=SPAN_ID, + name=SPAN_NAME, + start_time=start_time_pb, + end_time=end_time_pb) + + trace_pb = Trace( + project_id=PROJECT, + trace_id=TRACE_ID, + spans=[span_pb]) + + parse_result = self._call_fut(trace_pb) + + expected_result = { + 'projectId': PROJECT, + 'traceId': TRACE_ID, + 'spans': [ + { + 'spanId': str(SPAN_ID), + 'name': SPAN_NAME, + 'startTime': START_TIME.isoformat() + 'Z', + 'endTime': END_TIME.isoformat() + 'Z', + }, + ], + } + + self.assertEqual(parse_result, expected_result) + + @mock.patch('google.cloud.trace._gax.MessageToDict', + side_effect=TypeError) + def test_unregistered_type(self, msg_to_dict_mock): + trace_pb = mock.Mock(spec=['HasField']) + trace_pb.HasField.return_value = False + with self.assertRaises(TypeError): + self._call_fut(trace_pb) + + +class Test_make_gax_trace_api(unittest.TestCase): + + def _call_fut(self, client): + from google.cloud.trace._gax import make_gax_trace_api + + return make_gax_trace_api(client) + + def test_it(self): + from google.cloud.trace._gax import _TraceAPI + from google.cloud._http import DEFAULT_USER_AGENT + + credentials = object() + client = mock.Mock(_credentials=credentials, spec=['_credentials']) + channels = [] + channel_args = [] + generated_api_kwargs = [] + channel_obj = object() + generated = object() + + def make_channel(*args): + channel_args.append(args) + return channel_obj + + def generated_api(channel=None, **kwargs): + channels.append(channel) + generated_api_kwargs.append(kwargs) + return generated + + host = 'foo.apis.invalid' + generated_api.SERVICE_ADDRESS = host + + patch = mock.patch.multiple( + 'google.cloud.trace._gax', + TraceServiceClient=generated_api, + make_secure_channel=make_channel) + with patch: + trace_api = self._call_fut(client) + + self.assertEqual(channels, [channel_obj]) + self.assertEqual(channel_args, + [(credentials, DEFAULT_USER_AGENT, host)]) + + self.assertEqual(len(generated_api_kwargs), 1) + self.assertEqual(generated_api_kwargs[0]['lib_name'], 'gccl') + + self.assertIsInstance(trace_api, _TraceAPI) + self.assertIs(trace_api._gax_api, generated) + self.assertIs(trace_api.client, client) + + +class _GAXTraceAPI(_GAXBaseAPI): + def patch_traces(self, project_id, traces, options=None): + self._patch_traces_called_with = (project_id, traces, options) + + def get_trace(self, project_id, trace_id, options=None): + self._get_traces_called_with = (project_id, trace_id, options) + return self._get_trace_response + + def list_traces( + self, + project_id, + view=None, + page_size=None, + start_time=None, + end_time=None, + filter_=None, + order_by=None, + options=None): + self._list_traces_called_with = ( + project_id, + view, + page_size, + start_time, + end_time, + filter_, + order_by, + options) + return self._list_traces_response diff --git a/trace/tests/unit/test_client.py b/trace/tests/unit/test_client.py new file mode 100644 index 000000000000..6bf578d48654 --- /dev/null +++ b/trace/tests/unit/test_client.py @@ -0,0 +1,126 @@ +# Copyright 2017 Google 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. + +import unittest + +import mock + + +def _make_credentials(): + import google.auth.credentials + + return mock.Mock(spec=google.auth.credentials.Credentials) + + +class TestClient(unittest.TestCase): + + PROJECT = 'PROJECT' + + @staticmethod + def _get_target_class(): + from google.cloud.trace.client import Client + + return Client + + def _make_one(self, *args, **kw): + return self._get_target_class()(*args, **kw) + + def test_constructor(self): + credentials = _make_credentials() + client = self._make_one(project=self.PROJECT, credentials=credentials) + self.assertEqual(client.project, self.PROJECT) + + def test_trace_api(self): + clients = [] + api_obj = object() + + def make_api(client_obj): + clients.append(client_obj) + return api_obj + + credentials = _make_credentials() + client = self._make_one(project=self.PROJECT, credentials=credentials) + + patch = mock.patch( + 'google.cloud.trace.client.make_gax_trace_api', + new=make_api) + + with patch: + api = client.trace_api + + self.assertIs(api, api_obj) + self.assertEqual(clients, [client]) + + def test_trace(self): + from google.cloud.trace.trace import Trace + + TRACE_ID = '5e6e73b4131303cb6f5c9dfbaf104e33' + credentials = _make_credentials() + client = self._make_one(project=self.PROJECT, credentials=credentials) + trace = Trace(client=client, project_id=self.PROJECT, trace_id=TRACE_ID) + + self.assertIsInstance(trace, Trace) + self.assertIs(trace.client, client) + self.assertEqual(trace.project_id, self.PROJECT) + self.assertEqual(trace.trace_id, TRACE_ID) + + def test_patch_traces(self): + TRACES = 'fake_traces_for_test' + api = _DummyTraceAPI() + + api.patch_traces(project_id=self.PROJECT, traces=TRACES) + self.assertEqual(api._patch_traces_called_with, (self.PROJECT, TRACES, None)) + + def test_get_trace(self): + TRACE_ID = '5e6e73b4131303cb6f5c9dfbaf104e33' + api = _DummyTraceAPI() + + api.get_trace(project_id=self.PROJECT, trace_id=TRACE_ID) + self.assertEqual(api._get_traces_called_with, (self.PROJECT, TRACE_ID, None)) + + def test_list_traces(self): + api = _DummyTraceAPI() + + api.list_traces(project_id=self.PROJECT) + api.list_traces(api._list_traces_called_with, ( + self.PROJECT, + None,None,None,None,None,None,None)) + + +class _DummyTraceAPI(object): + def patch_traces(self, project_id, traces, options=None): + self._patch_traces_called_with = (project_id, traces, options) + + def get_trace(self, project_id, trace_id, options=None): + self._get_traces_called_with = (project_id, trace_id, options) + + def list_traces( + self, + project_id, + view=None, + page_size=None, + start_time=None, + end_time=None, + filter_=None, + order_by=None, + page_token=None): + self._list_traces_called_with = ( + project_id, + view, + page_size, + start_time, + end_time, + filter_, + order_by, + page_token) From 7ebb8baaffb1a81ecc971c34da878135a035b519 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Thu, 22 Jun 2017 17:51:47 -0700 Subject: [PATCH 06/16] Add unit test skeleton for trace.py and trace_span.py --- trace/google/cloud/trace/client.py | 2 +- trace/google/cloud/trace/trace_span.py | 25 +++--- trace/tests/unit/test__gax.py | 6 -- trace/tests/unit/test_client.py | 2 +- trace/tests/unit/test_trace.py | 79 +++++++++++++++++++ trace/tests/unit/test_trace_span.py | 102 +++++++++++++++++++++++++ 6 files changed, 197 insertions(+), 19 deletions(-) create mode 100644 trace/tests/unit/test_trace.py create mode 100644 trace/tests/unit/test_trace_span.py diff --git a/trace/google/cloud/trace/client.py b/trace/google/cloud/trace/client.py index 3f06c0c098f1..db8188690aa0 100644 --- a/trace/google/cloud/trace/client.py +++ b/trace/google/cloud/trace/client.py @@ -151,7 +151,7 @@ def list_traces( which the trace data was collected from the application. - :type filter: str + :type filter_: str :param filter_: (Optional) An optional filter for the request. :type order_by: str diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index ef8366513c2f..32dbae8e890d 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -50,12 +50,12 @@ class TraceSpan(object): Label keys must be less than 128 bytes. Label values must be less than 16 kilobytes. - :type start_time: :class:`~datetime.datetime` + :type start_time: str :param start_time: (Optional) Start of the time interval (inclusive) during which the trace data was collected from the application. - :type end_time: :class:`~datetime.datetime` + :type end_time: str :param end_time: (Optional) End of the time interval (inclusive) during which the trace data was collected from the application. @@ -80,7 +80,9 @@ def __init__( self.end_time = end_time if span_id is None: - self.span_id = self.generate_span_id() + span_id = generate_span_id() + + self.span_id = span_id self.child_spans = [] @@ -113,15 +115,16 @@ def __enter__(self): def __exit__(self, exception_type, exception_value, traceback): self.set_end_time() - def generate_span_id(self): - """Return the random generated span ID for a span. - :rtype: int - :returns: Identifier for the span. Must be a 64-bit integer other - than 0 and unique within a trace. Converted to string. - """ - span_id = str(random.getrandbits(64)) - return int(span_id) +def generate_span_id(self): + """Return the random generated span ID for a span. + + :rtype: int + :returns: Identifier for the span. Must be a 64-bit integer other + than 0 and unique within a trace. Converted to string. + """ + span_id = str(random.getrandbits(64)) + return int(span_id) def format_span_json(span): diff --git a/trace/tests/unit/test__gax.py b/trace/tests/unit/test__gax.py index 7d6a8adb9680..e81822f10d7a 100644 --- a/trace/tests/unit/test__gax.py +++ b/trace/tests/unit/test__gax.py @@ -19,12 +19,6 @@ from google.cloud._testing import _GAXBaseAPI -def _make_credentials(): - import google.auth.credentials - - return mock.Mock(spec=google.auth.credentials.Credentials) - - class _Base(object): PROJECT = 'PROJECT' PROJECT_PATH = 'projects/%s' % (PROJECT,) diff --git a/trace/tests/unit/test_client.py b/trace/tests/unit/test_client.py index 6bf578d48654..a699e5796e75 100644 --- a/trace/tests/unit/test_client.py +++ b/trace/tests/unit/test_client.py @@ -95,7 +95,7 @@ def test_list_traces(self): api.list_traces(project_id=self.PROJECT) api.list_traces(api._list_traces_called_with, ( self.PROJECT, - None,None,None,None,None,None,None)) + None, None, None, None, None, None, None)) class _DummyTraceAPI(object): diff --git a/trace/tests/unit/test_trace.py b/trace/tests/unit/test_trace.py new file mode 100644 index 000000000000..7986e660cfb2 --- /dev/null +++ b/trace/tests/unit/test_trace.py @@ -0,0 +1,79 @@ +# Copyright 2017 Google 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. + +import unittest + +import mock + + +class TestTrace(unittest.TestCase): + + PROJECT = 'PROJECT' + + @staticmethod + def _get_target_class(): + from google.cloud.trace.trace import Trace + + return Trace + + def _make_one(self, *args, **kw): + return self._get_target_class()(*args, **kw) + + def test_constructor_defaults(self): + TRACE_ID = 'test_trace_id' + + client = mock.Mock(project=self.PROJECT, spec=['project']) + patch = mock.patch( + 'google.cloud.trace.trace.generate_trace_id', + return_value=TRACE_ID) + + with patch: + trace = self._make_one(client) + + self.assertIs(trace.client, client) + self.assertEqual(trace.project_id, self.PROJECT) + self.assertEqual(trace.trace_id, TRACE_ID) + + def test_constructor_explicit(self): + TRACE_ID = 'test_trace_id' + + client = mock.Mock(project=self.PROJECT, spec=['project']) + trace = self._make_one( + client=client, + project_id=self.PROJECT, + trace_id=TRACE_ID) + + self.assertIs(trace.client, client) + self.assertEqual(trace.project_id, self.PROJECT) + self.assertEqual(trace.trace_id, TRACE_ID) + + def test_start(self): + pass + + def test_finish(self): + pass + + def test_span(self): + pass + + def test_send(self): + pass + + +class Test_traverse_span_tree(unittest.TestCase): + pass + + +class Test_generate_span_id(unittest.TestCase): + pass diff --git a/trace/tests/unit/test_trace_span.py b/trace/tests/unit/test_trace_span.py new file mode 100644 index 000000000000..e39b35a75292 --- /dev/null +++ b/trace/tests/unit/test_trace_span.py @@ -0,0 +1,102 @@ +# Copyright 2017 Google 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. + +import unittest + +import mock + + +class TestTraceSpan(unittest.TestCase): + + PROJECT = 'PROJECT' + + @staticmethod + def _get_target_class(): + from google.cloud.trace.trace_span import TraceSpan + + return TraceSpan + + def _make_one(self, *args, **kw): + return self._get_target_class()(*args, **kw) + + def test_constructor_defaults(self): + from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum + + SPAN_ID = 'test_span_id' + SPAN_NAME = 'test_span_name' + + patch = mock.patch( + 'google.cloud.trace.trace_span.generate_span_id', + return_value=SPAN_ID) + + with patch: + span = self._make_one(SPAN_NAME) + + self.assertEqual(span.name, SPAN_NAME) + self.assertEqual(span.span_id, SPAN_ID) + self.assertEqual(span.kind, Enum.SpanKind.SPAN_KIND_UNSPECIFIED) + self.assertIsNone(span.parent_span_id) + self.assertIsNone(span.labels) + self.assertIsNone(span.start_time) + self.assertIsNone(span.end_time) + + def test_constructor_explicit(self): + from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum + + from datetime import datetime + + SPAN_ID = 'test_span_id' + SPAN_NAME = 'test_span_name' + KIND = Enum.SpanKind.RPC_CLIENT + PARENT_SPAN_ID = 1234 + START_TIME = datetime.utcnow().isoformat() + 'Z' + END_TIME = datetime.utcnow().isoformat() + 'Z' + LABELS = { + '/http/status_code': '200', + '/component': 'HTTP load balancer', + } + + span = self._make_one( + name=SPAN_NAME, + kind=KIND, + parent_span_id=PARENT_SPAN_ID, + labels=LABELS, + start_time=START_TIME, + end_time=END_TIME, + span_id=SPAN_ID) + + self.assertEqual(span.name, SPAN_NAME) + self.assertEqual(span.span_id, SPAN_ID) + self.assertEqual(span.kind, KIND) + self.assertEqual(span.parent_span_id, PARENT_SPAN_ID) + self.assertEqual(span.labels, LABELS) + self.assertEqual(span.start_time, START_TIME) + self.assertEqual(span.end_time, END_TIME) + + def test_span(self): + pass + + def test_set_start_time(self): + pass + + def test_set_end_time(self): + pass + + +class Test_generate_span_id(unittest.TestCase): + pass + + +class Test_format_span_json(unittest.TestCase): + pass From 0bbd3d7247dbe201d6ff5aae921e09c038e36845 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Thu, 22 Jun 2017 18:29:34 -0700 Subject: [PATCH 07/16] Fix stuff --- trace/google/cloud/trace/trace_span.py | 2 +- trace/tests/unit/test_trace.py | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index 32dbae8e890d..d599d2033645 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -116,7 +116,7 @@ def __exit__(self, exception_type, exception_value, traceback): self.set_end_time() -def generate_span_id(self): +def generate_span_id(): """Return the random generated span ID for a span. :rtype: int diff --git a/trace/tests/unit/test_trace.py b/trace/tests/unit/test_trace.py index 7986e660cfb2..c1373777df95 100644 --- a/trace/tests/unit/test_trace.py +++ b/trace/tests/unit/test_trace.py @@ -59,13 +59,31 @@ def test_constructor_explicit(self): self.assertEqual(trace.trace_id, TRACE_ID) def test_start(self): - pass + client = object() + trace = self._make_one(client=client, project_id=self.PROJECT) + trace.start() + + self.assertEqual(trace.spans, []) def test_finish(self): pass def test_span(self): - pass + from google.cloud.trace.trace_span import TraceSpan + + SPAN_NAME = 'test_span_name' + + client = object() + trace = self._make_one(client=client, project_id=self.PROJECT) + trace.spans = [] + + trace.span(name=SPAN_NAME) + self.assertEqual(len(trace.spans), 1) + + result_span = trace.spans[0] + self.assertIsInstance(result_span, TraceSpan) + self.assertEqual(result_span.name, SPAN_NAME) + def test_send(self): pass From 95bd77b618aaaebf589cb1f8f9a3894a4a5eabf9 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Fri, 23 Jun 2017 13:21:01 -0700 Subject: [PATCH 08/16] Fix style --- trace/.coveragerc | 11 ++ trace/google/cloud/trace/_gax.py | 1 + trace/google/cloud/trace/trace.py | 6 +- trace/google/cloud/trace/trace_span.py | 4 +- trace/nox.py | 15 +- trace/tests/unit/test__gax.py | 207 +++++++++++++------------ trace/tests/unit/test_client.py | 34 ++-- trace/tests/unit/test_trace.py | 35 ++--- trace/tests/unit/test_trace_span.py | 58 +++---- 9 files changed, 198 insertions(+), 173 deletions(-) create mode 100644 trace/.coveragerc diff --git a/trace/.coveragerc b/trace/.coveragerc new file mode 100644 index 000000000000..55eb9505d7e9 --- /dev/null +++ b/trace/.coveragerc @@ -0,0 +1,11 @@ +[run] +branch = True + +[report] +fail_under = 100 +show_missing = True +exclude_lines = + # Re-enable the standard pragma + pragma: NO COVER + # Ignore debug-only repr + def __repr__ \ No newline at end of file diff --git a/trace/google/cloud/trace/_gax.py b/trace/google/cloud/trace/_gax.py index f05e1b11042e..b086fe089e44 100644 --- a/trace/google/cloud/trace/_gax.py +++ b/trace/google/cloud/trace/_gax.py @@ -136,6 +136,7 @@ def list_traces( """ if page_token is None: page_token = INITIAL_PAGE + options = CallOptions(page_token=page_token) page_iter = self._gax_api.list_traces( project_id=project_id, diff --git a/trace/google/cloud/trace/trace.py b/trace/google/cloud/trace/trace.py index d6f9a3425efe..71b41ec812c0 100644 --- a/trace/google/cloud/trace/trace.py +++ b/trace/google/cloud/trace/trace.py @@ -18,6 +18,8 @@ from google.cloud.trace.trace_span import format_span_json from subprocess import check_output +import uuid + _GENERATE_TRACE_ID_COMMAND = 'uuidgen | sed s/-//g' @@ -147,7 +149,5 @@ def generate_trace_id(): :rtype: str :returns: 32 digits randomly generated trace ID. """ - trace_id = check_output(_GENERATE_TRACE_ID_COMMAND, shell=True)\ - .decode('utf-8')\ - .rstrip('\n') + trace_id = str(uuid.uuid4()).replace('-', '') return trace_id diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index d599d2033645..db2f32988f8f 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -17,7 +17,7 @@ from datetime import datetime from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum -import random +import uuid class TraceSpan(object): @@ -123,7 +123,7 @@ def generate_span_id(): :returns: Identifier for the span. Must be a 64-bit integer other than 0 and unique within a trace. Converted to string. """ - span_id = str(random.getrandbits(64)) + span_id = uuid.uuid4().int & (1<<64)-1 return int(span_id) diff --git a/trace/nox.py b/trace/nox.py index 647b994358b0..4ce2dbc898fb 100644 --- a/trace/nox.py +++ b/trace/nox.py @@ -33,7 +33,18 @@ def unit_tests(session, python_version): session.install('-e', '.') # Run py.test against the unit tests. - session.run('py.test', '--quiet', 'tests/unit') + session.run( + 'py.test', + '--quiet', + '--cov=google.cloud.trace', + '--cov=tests.unit', + '--cov-append', + '--cov-config=.coveragerc', + '--cov-report=', + '--cov-fail-under=97', + 'tests/unit', + *session.posargs + ) @nox.session @@ -63,7 +74,7 @@ def cover(session): This outputs the coverage report aggregating coverage from the unit test runs (not system test runs), and then erases coverage data. """ - session.interpreter = 'python3.6' + session.interpreter = 'python2.7' session.install('coverage', 'pytest-cov') session.run('coverage', 'report', '--show-missing', '--fail-under=100') session.run('coverage', 'erase') diff --git a/trace/tests/unit/test__gax.py b/trace/tests/unit/test__gax.py index e81822f10d7a..67304ff99aff 100644 --- a/trace/tests/unit/test__gax.py +++ b/trace/tests/unit/test__gax.py @@ -20,8 +20,7 @@ class _Base(object): - PROJECT = 'PROJECT' - PROJECT_PATH = 'projects/%s' % (PROJECT,) + project = 'PROJECT' def _make_one(self, *args, **kw): return self._get_target_class()(*args, **kw) @@ -49,24 +48,23 @@ def test_patch_traces(self): from datetime import datetime - PROJECT = 'PROJECT' - TRACE_ID = 'test_trace_id' - SPAN_ID = 1234 - SPAN_NAME = 'test_span_name' - START_TIME = datetime.utcnow() - END_TIME = datetime.utcnow() + trace_id = 'test_trace_id' + span_id = 1234 + span_name = 'test_span_name' + start_time = datetime.utcnow() + end_time = datetime.utcnow() - TRACES = { + traces = { 'traces': [ { - 'projectId': PROJECT, - 'traceId': TRACE_ID, + 'projectId': self.project, + 'traceId': trace_id, 'spans': [ { - 'spanId': SPAN_ID, - 'name': SPAN_NAME, - 'startTime': START_TIME.isoformat() + 'Z', - 'endTime': END_TIME.isoformat() + 'Z', + 'spanId': span_id, + 'name': span_name, + 'startTime': start_time.isoformat() + 'Z', + 'endTime': end_time.isoformat() + 'Z', }, ], }, @@ -75,49 +73,48 @@ def test_patch_traces(self): gax_api = _GAXTraceAPI() api = self._make_one(gax_api, None) - api.patch_traces(project_id=PROJECT, traces=TRACES) - project_id, traces, options = (gax_api._patch_traces_called_with) + api.patch_traces(project_id=self.project, traces=traces) + project_id_called, traces_called, options_called = (gax_api._patch_traces_called_with) - self.assertEqual(len(traces.traces), 1) - trace = traces.traces[0] + self.assertEqual(len(traces_called.traces), 1) + trace = traces_called.traces[0] self.assertEqual(len(trace.spans), 1) span = trace.spans[0] - self.assertIsInstance(traces, Traces) - self.assertEqual(trace.project_id, PROJECT) - self.assertEqual(trace.trace_id, TRACE_ID) + self.assertIsInstance(traces_called, Traces) + self.assertEqual(trace.project_id, self.project) + self.assertEqual(trace.trace_id, trace_id) self.assertIsInstance(trace, Trace) - self.assertEqual(span.span_id, SPAN_ID) - self.assertEqual(span.name, SPAN_NAME) + self.assertEqual(span.span_id, span_id) + self.assertEqual(span.name, span_name) self.assertEqual( span.start_time, - _datetime_to_pb_timestamp(START_TIME)) + _datetime_to_pb_timestamp(start_time)) self.assertEqual( span.end_time, - _datetime_to_pb_timestamp(END_TIME)) + _datetime_to_pb_timestamp(end_time)) self.assertIsInstance(span, TraceSpan) - self.assertIsNone(options) + self.assertIsNone(options_called) def test_get_trace(self): from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( Trace) - PROJECT = 'PROJECT' - TRACE_ID = 'test_trace_id' - TRACE_PB = Trace(project_id=PROJECT, trace_id=TRACE_ID) + trace_id = 'test_trace_id' + trace_pb = Trace(project_id=self.project, trace_id=trace_id) - gax_api = _GAXTraceAPI(_get_trace_response=TRACE_PB) + gax_api = _GAXTraceAPI(_get_trace_response=trace_pb) api = self._make_one(gax_api, None) - api.get_trace(project_id=PROJECT, trace_id=TRACE_ID) + api.get_trace(project_id=self.project, trace_id=trace_id) - project_id, trace_id, options = gax_api._get_traces_called_with - self.assertEqual(project_id, PROJECT) - self.assertEqual(trace_id, TRACE_ID) - self.assertIsNone(options) + project_id_called, trace_id_called, options_called = gax_api._get_traces_called_with + self.assertEqual(project_id_called, self.project) + self.assertEqual(trace_id_called, trace_id) + self.assertIsNone(options_called) def _make_trace_pb( self, @@ -131,9 +128,9 @@ def _make_trace_pb( labels): from google.cloud.trace._gax import _traces_mapping_to_pb - SPAN_KIND = 2 + span_kind = 2 - TRACES = { + traces = { 'traces': [ { 'projectId': project, @@ -144,7 +141,7 @@ def _make_trace_pb( 'name': span_name, 'startTime': start_time, 'endTime': end_time, - 'kind': SPAN_KIND, + 'kind': span_kind, 'parentSpanId': parent_span_id, 'labels': labels, }, @@ -153,7 +150,7 @@ def _make_trace_pb( ], } - traces_pb = _traces_mapping_to_pb(TRACES) + traces_pb = _traces_mapping_to_pb(traces) trace_pb = traces_pb.traces return trace_pb @@ -163,42 +160,40 @@ def test_list_traces(self): from datetime import datetime - PROJECT = 'PROJECT' - TRACE_ID = 'test_trace_id' - SPAN_ID = 1234 - SPAN_NAME = 'test_span_name' - SPAN_KIND = 'RPC_CLIENT' - PARENT_SPAN_ID = 123 - START_TIME = datetime.utcnow().isoformat() + 'Z' - END_TIME = datetime.utcnow().isoformat() + 'Z' - LABELS = { + trace_id = 'test_trace_id' + span_id = 1234 + span_name = 'test_span_name' + span_kind = 'RPC_CLIENT' + parent_span_id = 123 + start_time = datetime.utcnow().isoformat() + 'Z' + end_time = datetime.utcnow().isoformat() + 'Z' + labels = { '/http/status_code': '200', '/component': 'HTTP load balancer', } - - SIZE = 10 - TOKEN = 'TOKEN' - VIEW_TYPE = Enum.ViewType.COMPLETE + size = 10 + token = 'TOKEN' + view_type = Enum.ViewType.COMPLETE trace_pb = self._make_trace_pb( - PROJECT, - TRACE_ID, - SPAN_ID, - SPAN_NAME, - START_TIME, - END_TIME, - PARENT_SPAN_ID, - LABELS) + self.project, + trace_id, + span_id, + span_name, + start_time, + end_time, + parent_span_id, + labels) response = _GAXPageIterator(trace_pb) gax_api = _GAXTraceAPI(_list_traces_response=response) api = self._make_one(gax_api, None) iterator = api.list_traces( - project_id=PROJECT, - view=VIEW_TYPE, - page_size=SIZE, - page_token=TOKEN) + project_id=self.project, + view=view_type, + page_size=size, + page_token=token) traces = list(iterator) @@ -208,31 +203,39 @@ def test_list_traces(self): self.assertEqual(len(trace['spans']), 1) span = trace['spans'][0] - self.assertEqual(trace['projectId'], PROJECT) - self.assertEqual(trace['traceId'], TRACE_ID) + self.assertEqual(trace['projectId'], self.project) + self.assertEqual(trace['traceId'], trace_id) - self.assertEqual(span['spanId'], str(SPAN_ID)) - self.assertEqual(span['name'], SPAN_NAME) + self.assertEqual(span['spanId'], str(span_id)) + self.assertEqual(span['name'], span_name) self.assertEqual( - span['startTime'], START_TIME) + span['startTime'], start_time) self.assertEqual( - span['endTime'], END_TIME) - self.assertEqual(span['kind'], SPAN_KIND) - self.assertEqual(span['parentSpanId'], str(PARENT_SPAN_ID)) - self.assertEqual(span['labels'], LABELS) - - project_id, view, page_size, start_time, end_time, filter_, order_by, options = ( + span['endTime'], end_time) + self.assertEqual(span['kind'], span_kind) + self.assertEqual(span['parentSpanId'], str(parent_span_id)) + self.assertEqual(span['labels'], labels) + + project_id_called,\ + view_called,\ + page_size_called,\ + start_time_called,\ + end_time_called,\ + filter__called,\ + order_by_called,\ + options_called = ( gax_api._list_traces_called_with ) - self.assertEqual(project_id, PROJECT) - self.assertEqual(view, VIEW_TYPE) - self.assertEqual(page_size, SIZE) - self.assertIsNone(start_time) - self.assertIsNone(end_time) - self.assertIsNone(filter_) - self.assertIsNone(order_by) - self.assertEqual(options.page_token, TOKEN) + + self.assertEqual(project_id_called, self.project) + self.assertEqual(view_called, view_type) + self.assertEqual(page_size_called, size) + self.assertIsNone(start_time_called) + self.assertIsNone(end_time_called) + self.assertIsNone(filter__called) + self.assertIsNone(order_by_called) + self.assertEqual(options_called.page_token, token) class Test__parse_trace_pb(unittest.TestCase): @@ -250,38 +253,38 @@ def test_registered_type(self): from datetime import datetime - PROJECT = u'PROJECT' - TRACE_ID = u'test_trace_id' - SPAN_ID = 1234 - SPAN_NAME = u'test_span_name' - START_TIME = datetime.utcnow() - END_TIME = datetime.utcnow() + project = u'PROJECT' + trace_id = u'test_trace_id' + span_id = 1234 + span_name = u'test_span_name' + start_time = datetime.utcnow() + end_time = datetime.utcnow() - start_time_pb = _datetime_to_pb_timestamp(START_TIME) - end_time_pb = _datetime_to_pb_timestamp(END_TIME) + start_time_pb = _datetime_to_pb_timestamp(start_time) + end_time_pb = _datetime_to_pb_timestamp(end_time) span_pb = TraceSpan( - span_id=SPAN_ID, - name=SPAN_NAME, + span_id=span_id, + name=span_name, start_time=start_time_pb, end_time=end_time_pb) trace_pb = Trace( - project_id=PROJECT, - trace_id=TRACE_ID, + project_id=project, + trace_id=trace_id, spans=[span_pb]) parse_result = self._call_fut(trace_pb) expected_result = { - 'projectId': PROJECT, - 'traceId': TRACE_ID, + 'projectId': project, + 'traceId': trace_id, 'spans': [ { - 'spanId': str(SPAN_ID), - 'name': SPAN_NAME, - 'startTime': START_TIME.isoformat() + 'Z', - 'endTime': END_TIME.isoformat() + 'Z', + 'spanId': str(span_id), + 'name': span_name, + 'startTime': start_time.isoformat() + 'Z', + 'endTime': end_time.isoformat() + 'Z', }, ], } diff --git a/trace/tests/unit/test_client.py b/trace/tests/unit/test_client.py index a699e5796e75..48c252d0568a 100644 --- a/trace/tests/unit/test_client.py +++ b/trace/tests/unit/test_client.py @@ -25,7 +25,7 @@ def _make_credentials(): class TestClient(unittest.TestCase): - PROJECT = 'PROJECT' + project = 'PROJECT' @staticmethod def _get_target_class(): @@ -38,8 +38,8 @@ def _make_one(self, *args, **kw): def test_constructor(self): credentials = _make_credentials() - client = self._make_one(project=self.PROJECT, credentials=credentials) - self.assertEqual(client.project, self.PROJECT) + client = self._make_one(project=self.project, credentials=credentials) + self.assertEqual(client.project, self.project) def test_trace_api(self): clients = [] @@ -50,7 +50,7 @@ def make_api(client_obj): return api_obj credentials = _make_credentials() - client = self._make_one(project=self.PROJECT, credentials=credentials) + client = self._make_one(project=self.project, credentials=credentials) patch = mock.patch( 'google.cloud.trace.client.make_gax_trace_api', @@ -65,36 +65,36 @@ def make_api(client_obj): def test_trace(self): from google.cloud.trace.trace import Trace - TRACE_ID = '5e6e73b4131303cb6f5c9dfbaf104e33' + trace_id = '5e6e73b4131303cb6f5c9dfbaf104e33' credentials = _make_credentials() - client = self._make_one(project=self.PROJECT, credentials=credentials) - trace = Trace(client=client, project_id=self.PROJECT, trace_id=TRACE_ID) + client = self._make_one(project=self.project, credentials=credentials) + trace = Trace(client=client, project_id=self.project, trace_id=trace_id) self.assertIsInstance(trace, Trace) self.assertIs(trace.client, client) - self.assertEqual(trace.project_id, self.PROJECT) - self.assertEqual(trace.trace_id, TRACE_ID) + self.assertEqual(trace.project_id, self.project) + self.assertEqual(trace.trace_id, trace_id) def test_patch_traces(self): - TRACES = 'fake_traces_for_test' + traces = 'fake_traces_for_test' api = _DummyTraceAPI() - api.patch_traces(project_id=self.PROJECT, traces=TRACES) - self.assertEqual(api._patch_traces_called_with, (self.PROJECT, TRACES, None)) + api.patch_traces(project_id=self.project, traces=traces) + self.assertEqual(api._patch_traces_called_with, (self.project, traces, None)) def test_get_trace(self): - TRACE_ID = '5e6e73b4131303cb6f5c9dfbaf104e33' + trace_id = '5e6e73b4131303cb6f5c9dfbaf104e33' api = _DummyTraceAPI() - api.get_trace(project_id=self.PROJECT, trace_id=TRACE_ID) - self.assertEqual(api._get_traces_called_with, (self.PROJECT, TRACE_ID, None)) + api.get_trace(project_id=self.project, trace_id=trace_id) + self.assertEqual(api._get_traces_called_with, (self.project, trace_id, None)) def test_list_traces(self): api = _DummyTraceAPI() - api.list_traces(project_id=self.PROJECT) + api.list_traces(project_id=self.project) api.list_traces(api._list_traces_called_with, ( - self.PROJECT, + self.project, None, None, None, None, None, None, None)) diff --git a/trace/tests/unit/test_trace.py b/trace/tests/unit/test_trace.py index c1373777df95..89e5d1d62a05 100644 --- a/trace/tests/unit/test_trace.py +++ b/trace/tests/unit/test_trace.py @@ -19,7 +19,7 @@ class TestTrace(unittest.TestCase): - PROJECT = 'PROJECT' + project = 'PROJECT' @staticmethod def _get_target_class(): @@ -31,36 +31,36 @@ def _make_one(self, *args, **kw): return self._get_target_class()(*args, **kw) def test_constructor_defaults(self): - TRACE_ID = 'test_trace_id' + trace_id = 'test_trace_id' - client = mock.Mock(project=self.PROJECT, spec=['project']) + client = mock.Mock(project=self.project, spec=['project']) patch = mock.patch( 'google.cloud.trace.trace.generate_trace_id', - return_value=TRACE_ID) + return_value=trace_id) with patch: trace = self._make_one(client) self.assertIs(trace.client, client) - self.assertEqual(trace.project_id, self.PROJECT) - self.assertEqual(trace.trace_id, TRACE_ID) + self.assertEqual(trace.project_id, self.project) + self.assertEqual(trace.trace_id, trace_id) def test_constructor_explicit(self): - TRACE_ID = 'test_trace_id' + trace_id = 'test_trace_id' - client = mock.Mock(project=self.PROJECT, spec=['project']) + client = mock.Mock(project=self.project, spec=['project']) trace = self._make_one( client=client, - project_id=self.PROJECT, - trace_id=TRACE_ID) + project_id=self.project, + trace_id=trace_id) self.assertIs(trace.client, client) - self.assertEqual(trace.project_id, self.PROJECT) - self.assertEqual(trace.trace_id, TRACE_ID) + self.assertEqual(trace.project_id, self.project) + self.assertEqual(trace.trace_id, trace_id) def test_start(self): client = object() - trace = self._make_one(client=client, project_id=self.PROJECT) + trace = self._make_one(client=client, project_id=self.project) trace.start() self.assertEqual(trace.spans, []) @@ -71,19 +71,18 @@ def test_finish(self): def test_span(self): from google.cloud.trace.trace_span import TraceSpan - SPAN_NAME = 'test_span_name' + span_name = 'test_span_name' client = object() - trace = self._make_one(client=client, project_id=self.PROJECT) + trace = self._make_one(client=client, project_id=self.project) trace.spans = [] - trace.span(name=SPAN_NAME) + trace.span(name=span_name) self.assertEqual(len(trace.spans), 1) result_span = trace.spans[0] self.assertIsInstance(result_span, TraceSpan) - self.assertEqual(result_span.name, SPAN_NAME) - + self.assertEqual(result_span.name, span_name) def test_send(self): pass diff --git a/trace/tests/unit/test_trace_span.py b/trace/tests/unit/test_trace_span.py index e39b35a75292..ddf442fde938 100644 --- a/trace/tests/unit/test_trace_span.py +++ b/trace/tests/unit/test_trace_span.py @@ -19,7 +19,7 @@ class TestTraceSpan(unittest.TestCase): - PROJECT = 'PROJECT' + project = 'PROJECT' @staticmethod def _get_target_class(): @@ -33,18 +33,18 @@ def _make_one(self, *args, **kw): def test_constructor_defaults(self): from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum - SPAN_ID = 'test_span_id' - SPAN_NAME = 'test_span_name' + span_id = 'test_span_id' + span_name = 'test_span_name' patch = mock.patch( 'google.cloud.trace.trace_span.generate_span_id', - return_value=SPAN_ID) + return_value=span_id) with patch: - span = self._make_one(SPAN_NAME) + span = self._make_one(span_name) - self.assertEqual(span.name, SPAN_NAME) - self.assertEqual(span.span_id, SPAN_ID) + self.assertEqual(span.name, span_name) + self.assertEqual(span.span_id, span_id) self.assertEqual(span.kind, Enum.SpanKind.SPAN_KIND_UNSPECIFIED) self.assertIsNone(span.parent_span_id) self.assertIsNone(span.labels) @@ -56,33 +56,33 @@ def test_constructor_explicit(self): from datetime import datetime - SPAN_ID = 'test_span_id' - SPAN_NAME = 'test_span_name' - KIND = Enum.SpanKind.RPC_CLIENT - PARENT_SPAN_ID = 1234 - START_TIME = datetime.utcnow().isoformat() + 'Z' - END_TIME = datetime.utcnow().isoformat() + 'Z' - LABELS = { + span_id = 'test_span_id' + span_name = 'test_span_name' + kind = Enum.SpanKind.RPC_CLIENT + parent_span_id = 1234 + start_time = datetime.utcnow().isoformat() + 'Z' + end_time = datetime.utcnow().isoformat() + 'Z' + labels = { '/http/status_code': '200', '/component': 'HTTP load balancer', } span = self._make_one( - name=SPAN_NAME, - kind=KIND, - parent_span_id=PARENT_SPAN_ID, - labels=LABELS, - start_time=START_TIME, - end_time=END_TIME, - span_id=SPAN_ID) - - self.assertEqual(span.name, SPAN_NAME) - self.assertEqual(span.span_id, SPAN_ID) - self.assertEqual(span.kind, KIND) - self.assertEqual(span.parent_span_id, PARENT_SPAN_ID) - self.assertEqual(span.labels, LABELS) - self.assertEqual(span.start_time, START_TIME) - self.assertEqual(span.end_time, END_TIME) + name=span_name, + kind=kind, + parent_span_id=parent_span_id, + labels=labels, + start_time=start_time, + end_time=end_time, + span_id=span_id) + + self.assertEqual(span.name, span_name) + self.assertEqual(span.span_id, span_id) + self.assertEqual(span.kind, kind) + self.assertEqual(span.parent_span_id, parent_span_id) + self.assertEqual(span.labels, labels) + self.assertEqual(span.start_time, start_time) + self.assertEqual(span.end_time, end_time) def test_span(self): pass From d5186f55c19baaf0ae5817028fe191f21eb9302d Mon Sep 17 00:00:00 2001 From: Angela Li Date: Fri, 23 Jun 2017 17:19:05 -0700 Subject: [PATCH 09/16] Fix stuff --- trace/.coveragerc | 2 +- trace/google/cloud/trace/__init__.py | 3 +- trace/google/cloud/trace/_gax.py | 10 +- trace/google/cloud/trace/client.py | 1 + trace/google/cloud/trace/trace.py | 7 +- trace/google/cloud/trace/trace_span.py | 8 +- trace/tests/unit/test__gax.py | 130 ++++++++++++++++++++---- trace/tests/unit/test_client.py | 134 +++++++++++++++++-------- trace/tests/unit/test_trace_span.py | 2 + 9 files changed, 223 insertions(+), 74 deletions(-) diff --git a/trace/.coveragerc b/trace/.coveragerc index 55eb9505d7e9..a54b99aa14b7 100644 --- a/trace/.coveragerc +++ b/trace/.coveragerc @@ -8,4 +8,4 @@ exclude_lines = # Re-enable the standard pragma pragma: NO COVER # Ignore debug-only repr - def __repr__ \ No newline at end of file + def __repr__ diff --git a/trace/google/cloud/trace/__init__.py b/trace/google/cloud/trace/__init__.py index 134ba8e00baf..461d41be3b20 100644 --- a/trace/google/cloud/trace/__init__.py +++ b/trace/google/cloud/trace/__init__.py @@ -12,10 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from google.cloud.trace._gax import _TraceAPI from google.cloud.trace.client import Client from google.cloud.trace.trace import Trace from google.cloud.trace.trace_span import TraceSpan -__all__ = ['_TraceAPI', 'Client', 'Trace', 'TraceSpan'] +__all__ = ['Client', 'Trace', 'TraceSpan'] diff --git a/trace/google/cloud/trace/_gax.py b/trace/google/cloud/trace/_gax.py index b086fe089e44..8e9795b4a286 100644 --- a/trace/google/cloud/trace/_gax.py +++ b/trace/google/cloud/trace/_gax.py @@ -14,8 +14,7 @@ """GAX Wrapper for interacting with the Stackdriver Trace API.""" -from google.cloud.gapic.trace.v1.trace_service_client import ( - TraceServiceClient) +from google.cloud.gapic.trace.v1 import trace_service_client from google.cloud.trace._helper import _traces_mapping_to_pb from google.gax import CallOptions from google.gax import INITIAL_PAGE @@ -136,7 +135,6 @@ def list_traces( """ if page_token is None: page_token = INITIAL_PAGE - options = CallOptions(page_token=page_token) page_iter = self._gax_api.list_traces( project_id=project_id, @@ -193,6 +191,8 @@ def make_gax_trace_api(client): channel = make_secure_channel( client._credentials, DEFAULT_USER_AGENT, - TraceServiceClient.SERVICE_ADDRESS) - generated = TraceServiceClient(channel=channel, lib_name='gccl') + trace_service_client.TraceServiceClient.SERVICE_ADDRESS) + generated = trace_service_client.TraceServiceClient( + channel=channel, + lib_name='gccl') return _TraceAPI(generated, client) diff --git a/trace/google/cloud/trace/client.py b/trace/google/cloud/trace/client.py index db8188690aa0..0195692b5f78 100644 --- a/trace/google/cloud/trace/client.py +++ b/trace/google/cloud/trace/client.py @@ -65,6 +65,7 @@ def trace(self, project_id=None, trace_id=None): """ if project_id is None: project_id = self.project + return Trace(client=self, project_id=project_id, trace_id=trace_id) def patch_traces(self, traces, project_id=None, options=None): diff --git a/trace/google/cloud/trace/trace.py b/trace/google/cloud/trace/trace.py index 71b41ec812c0..c43d8be7533a 100644 --- a/trace/google/cloud/trace/trace.py +++ b/trace/google/cloud/trace/trace.py @@ -16,17 +16,16 @@ from google.cloud.trace.trace_span import TraceSpan from google.cloud.trace.trace_span import format_span_json -from subprocess import check_output import uuid -_GENERATE_TRACE_ID_COMMAND = 'uuidgen | sed s/-//g' - class Trace(object): """A trace describes how long it takes for an application to perform an operation. It consists of a set of spans, each of which represent a single timed event within the operation. + + Node that Trace is not thread-safe and must not be shared between threads. See https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. @@ -137,7 +136,7 @@ def traverse_span_tree(root_span): cur_span = span_queue.pop(0) span_list.append(format_span_json(cur_span)) - for child_span in cur_span.child_spans: + for child_span in cur_span.children: span_queue.append(child_span) return span_list diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index db2f32988f8f..c9b0bde276b7 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -83,8 +83,12 @@ def __init__( span_id = generate_span_id() self.span_id = span_id + self._child_spans = [] - self.child_spans = [] + @property + def children(self): + """The child spans of the current span.""" + return self._child_spans def span(self, name='child_span'): """Create a child span for the current span and append it to the child @@ -97,7 +101,7 @@ def span(self, name='child_span'): :returns: A child TraceSpan to be added to the current span. """ child_span = TraceSpan(name, parent_span_id=self.span_id) - self.child_spans.append(child_span) + self._child_spans.append(child_span) return child_span def set_start_time(self): diff --git a/trace/tests/unit/test__gax.py b/trace/tests/unit/test__gax.py index 67304ff99aff..86899a99089e 100644 --- a/trace/tests/unit/test__gax.py +++ b/trace/tests/unit/test__gax.py @@ -154,9 +154,10 @@ def _make_trace_pb( trace_pb = traces_pb.traces return trace_pb - def test_list_traces(self): + def test_list_traces_no_paging(self): from google.cloud._testing import _GAXPageIterator from google.cloud.gapic.trace.v1.enums import ListTracesRequest as Enum + from google.gax import INITIAL_PAGE from datetime import datetime @@ -172,7 +173,6 @@ def test_list_traces(self): '/component': 'HTTP load balancer', } size = 10 - token = 'TOKEN' view_type = Enum.ViewType.COMPLETE trace_pb = self._make_trace_pb( @@ -189,6 +189,89 @@ def test_list_traces(self): gax_api = _GAXTraceAPI(_list_traces_response=response) api = self._make_one(gax_api, None) + iterator = api.list_traces( + project_id=self.project, + view=view_type, + page_size=size) + + traces = list(iterator) + + self.assertEqual(len(traces), 1) + trace = traces[0] + + self.assertEqual(len(trace['spans']), 1) + span = trace['spans'][0] + + self.assertEqual(trace['projectId'], self.project) + self.assertEqual(trace['traceId'], trace_id) + + self.assertEqual(span['spanId'], str(span_id)) + self.assertEqual(span['name'], span_name) + + self.assertEqual( + span['startTime'], start_time) + self.assertEqual( + span['endTime'], end_time) + self.assertEqual(span['kind'], span_kind) + self.assertEqual(span['parentSpanId'], str(parent_span_id)) + self.assertEqual(span['labels'], labels) + + project_id_called,\ + view_called,\ + page_size_called,\ + start_time_called,\ + end_time_called,\ + filter__called,\ + order_by_called,\ + options_called = ( + gax_api._list_traces_called_with + ) + + self.assertEqual(project_id_called, self.project) + self.assertEqual(view_called, view_type) + self.assertEqual(page_size_called, size) + self.assertIsNone(start_time_called) + self.assertIsNone(end_time_called) + self.assertIsNone(filter__called) + self.assertIsNone(order_by_called) + self.assertEqual(options_called.page_token, INITIAL_PAGE) + + def test_list_traces_with_paging(self): + from google.cloud._testing import _GAXPageIterator + from google.cloud.gapic.trace.v1.enums import ListTracesRequest as Enum + from google.gax import INITIAL_PAGE + + from datetime import datetime + + trace_id = 'test_trace_id' + span_id = 1234 + span_name = 'test_span_name' + span_kind = 'RPC_CLIENT' + parent_span_id = 123 + start_time = datetime.utcnow().isoformat() + 'Z' + end_time = datetime.utcnow().isoformat() + 'Z' + labels = { + '/http/status_code': '200', + '/component': 'HTTP load balancer', + } + size = 10 + view_type = Enum.ViewType.COMPLETE + token = 'TOKEN' + + trace_pb = self._make_trace_pb( + self.project, + trace_id, + span_id, + span_name, + start_time, + end_time, + parent_span_id, + labels) + + response = _GAXPageIterator(trace_pb) + gax_api = _GAXTraceAPI(_list_traces_response=response) + api = self._make_one(gax_api, None) + iterator = api.list_traces( project_id=self.project, view=view_type, @@ -247,21 +330,23 @@ def _call_fut(*args, **kwargs): return _parse_trace_pb(*args, **kwargs) def test_registered_type(self): - from google.cloud._helpers import _datetime_to_pb_timestamp from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( TraceSpan, Trace) - - from datetime import datetime + from google.protobuf.timestamp_pb2 import Timestamp project = u'PROJECT' trace_id = u'test_trace_id' span_id = 1234 span_name = u'test_span_name' - start_time = datetime.utcnow() - end_time = datetime.utcnow() + start_time = '2017-06-24T00:12:50.369990Z' + end_time = '2017-06-24T00:13:39.633255Z' + start_seconds = 1498263170 + start_nanos = 369990000 + end_seconds = 1498263219 + end_nanos = 633255000 - start_time_pb = _datetime_to_pb_timestamp(start_time) - end_time_pb = _datetime_to_pb_timestamp(end_time) + start_time_pb = Timestamp(seconds=start_seconds, nanos=start_nanos) + end_time_pb = Timestamp(seconds=end_seconds, nanos=end_nanos) span_pb = TraceSpan( span_id=span_id, @@ -283,8 +368,8 @@ def test_registered_type(self): { 'spanId': str(span_id), 'name': span_name, - 'startTime': start_time.isoformat() + 'Z', - 'endTime': end_time.isoformat() + 'Z', + 'startTime': start_time, + 'endTime': end_time, }, ], } @@ -331,12 +416,17 @@ def generated_api(channel=None, **kwargs): host = 'foo.apis.invalid' generated_api.SERVICE_ADDRESS = host - patch = mock.patch.multiple( - 'google.cloud.trace._gax', - TraceServiceClient=generated_api, - make_secure_channel=make_channel) - with patch: - trace_api = self._call_fut(client) + patch_channel = mock.patch( + 'google.cloud.trace._gax.make_secure_channel', + new=make_channel) + + patch_api = mock.patch( + 'google.cloud.trace._gax.trace_service_client.TraceServiceClient', + new=generated_api) + + with patch_api: + with patch_channel: + trace_api = self._call_fut(client) self.assertEqual(channels, [channel_obj]) self.assertEqual(channel_args, @@ -378,3 +468,9 @@ def list_traces( order_by, options) return self._list_traces_response + + +def _datetime_to_rfc3339_w_nanos(value): + from google.cloud._helpers import _RFC3339_NO_FRACTION + no_fraction = value.strftime(_RFC3339_NO_FRACTION) + return '%s.%09dZ' % (no_fraction, value.microsecond * 1000) diff --git a/trace/tests/unit/test_client.py b/trace/tests/unit/test_client.py index 48c252d0568a..438b20151ee5 100644 --- a/trace/tests/unit/test_client.py +++ b/trace/tests/unit/test_client.py @@ -62,65 +62,113 @@ def make_api(client_obj): self.assertIs(api, api_obj) self.assertEqual(clients, [client]) - def test_trace(self): + def test_trace_default(self): from google.cloud.trace.trace import Trace trace_id = '5e6e73b4131303cb6f5c9dfbaf104e33' credentials = _make_credentials() client = self._make_one(project=self.project, credentials=credentials) - trace = Trace(client=client, project_id=self.project, trace_id=trace_id) + result_trace = client.trace(trace_id=trace_id) - self.assertIsInstance(trace, Trace) - self.assertIs(trace.client, client) - self.assertEqual(trace.project_id, self.project) - self.assertEqual(trace.trace_id, trace_id) + self.assertIsInstance(result_trace, Trace) + self.assertIs(result_trace.client, client) + self.assertEqual(result_trace.project_id, self.project) + self.assertEqual(result_trace.trace_id, trace_id) + + def test_trace_explicit(self): + from google.cloud.trace.trace import Trace + + trace_id = '5e6e73b4131303cb6f5c9dfbaf104e33' + credentials = _make_credentials() + client = self._make_one(project=self.project, credentials=credentials) + result_trace = client.trace(project_id=self.project, trace_id=trace_id) + + self.assertIsInstance(result_trace, Trace) + self.assertIs(result_trace.client, client) + self.assertEqual(result_trace.project_id, self.project) + self.assertEqual(result_trace.trace_id, trace_id) def test_patch_traces(self): + from google.cloud.trace._gax import _TraceAPI + + def patch_traces(project_id, traces, options=None): + _patch_traces_called_with = (project_id, traces, options) + return _patch_traces_called_with + + credentials = _make_credentials() + client = self._make_one(project=self.project, credentials=credentials) traces = 'fake_traces_for_test' - api = _DummyTraceAPI() - api.patch_traces(project_id=self.project, traces=traces) - self.assertEqual(api._patch_traces_called_with, (self.project, traces, None)) + mock_trace_api = mock.Mock(spec=_TraceAPI) + mock_trace_api.patch_traces = patch_traces + patch = mock.patch('google.cloud.trace.client.make_gax_trace_api', return_value=mock_trace_api) + + with patch: + patch_traces_called_with = client.patch_traces( + project_id=self.project, + traces=traces) + + self.assertEqual(patch_traces_called_with, (self.project, traces, None)) def test_get_trace(self): + from google.cloud.trace._gax import _TraceAPI + + def get_trace(trace_id, project_id=None, options=None): + _get_trace_called_with = (trace_id, project_id, options) + return _get_trace_called_with + + credentials = _make_credentials() + client = self._make_one(project=self.project, credentials=credentials) trace_id = '5e6e73b4131303cb6f5c9dfbaf104e33' - api = _DummyTraceAPI() - api.get_trace(project_id=self.project, trace_id=trace_id) - self.assertEqual(api._get_traces_called_with, (self.project, trace_id, None)) + mock_trace_api = mock.Mock(spec=_TraceAPI) + mock_trace_api.get_trace = get_trace + patch = mock.patch('google.cloud.trace.client.make_gax_trace_api', + return_value=mock_trace_api) + + with patch: + get_trace_called_with = client.get_trace( + trace_id=trace_id, + project_id=self.project) + + self.assertEqual(get_trace_called_with, + (trace_id, self.project, None)) def test_list_traces(self): - api = _DummyTraceAPI() + from google.cloud.trace._gax import _TraceAPI + + def list_traces( + project_id, + view=None, + page_size=None, + start_time=None, + end_time=None, + filter_=None, + order_by=None, + page_token=None): + _list_traces_called_with = ( + project_id, + view, + page_size, + start_time, + end_time, + filter_, + order_by, + page_token) + return _list_traces_called_with - api.list_traces(project_id=self.project) - api.list_traces(api._list_traces_called_with, ( - self.project, - None, None, None, None, None, None, None)) + credentials = _make_credentials() + client = self._make_one(project=self.project, credentials=credentials) + mock_trace_api = mock.Mock(spec=_TraceAPI) + mock_trace_api.list_traces = list_traces + patch = mock.patch('google.cloud.trace.client.make_gax_trace_api', + return_value=mock_trace_api) -class _DummyTraceAPI(object): - def patch_traces(self, project_id, traces, options=None): - self._patch_traces_called_with = (project_id, traces, options) - - def get_trace(self, project_id, trace_id, options=None): - self._get_traces_called_with = (project_id, trace_id, options) - - def list_traces( - self, - project_id, - view=None, - page_size=None, - start_time=None, - end_time=None, - filter_=None, - order_by=None, - page_token=None): - self._list_traces_called_with = ( - project_id, - view, - page_size, - start_time, - end_time, - filter_, - order_by, - page_token) + with patch: + list_traces_called_with = client.list_traces( + project_id=self.project) + + self.assertEqual(list_traces_called_with, ( + self.project, + None, None, None, None, None, None, None)) diff --git a/trace/tests/unit/test_trace_span.py b/trace/tests/unit/test_trace_span.py index ddf442fde938..899b8f6521b4 100644 --- a/trace/tests/unit/test_trace_span.py +++ b/trace/tests/unit/test_trace_span.py @@ -50,6 +50,7 @@ def test_constructor_defaults(self): self.assertIsNone(span.labels) self.assertIsNone(span.start_time) self.assertIsNone(span.end_time) + self.assertEqual(span.children, []) def test_constructor_explicit(self): from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum @@ -83,6 +84,7 @@ def test_constructor_explicit(self): self.assertEqual(span.labels, labels) self.assertEqual(span.start_time, start_time) self.assertEqual(span.end_time, end_time) + self.assertEqual(span.children, []) def test_span(self): pass From da122642bd5704986fc005c469fce5f2bcf25adb Mon Sep 17 00:00:00 2001 From: Angela Li Date: Mon, 26 Jun 2017 10:30:38 -0700 Subject: [PATCH 10/16] Fix test coverage --- trace/MANIFEST.in | 3 +- trace/nox.py | 5 +- trace/tests/unit/test_client.py | 122 +++++++++++++++++++++++++++++--- 3 files changed, 118 insertions(+), 12 deletions(-) diff --git a/trace/MANIFEST.in b/trace/MANIFEST.in index 5fffff88c833..fc33e282e3be 100644 --- a/trace/MANIFEST.in +++ b/trace/MANIFEST.in @@ -1,5 +1,6 @@ include README.rst LICENSE -global-include *.json +recursive-include unit_tests * +global-include google *.json *.proto graft google global-exclude *.py[co] global-exclude __pycache__ diff --git a/trace/nox.py b/trace/nox.py index 4ce2dbc898fb..08c69cb85df5 100644 --- a/trace/nox.py +++ b/trace/nox.py @@ -37,12 +37,11 @@ def unit_tests(session, python_version): 'py.test', '--quiet', '--cov=google.cloud.trace', - '--cov=tests.unit', '--cov-append', '--cov-config=.coveragerc', '--cov-report=', '--cov-fail-under=97', - 'tests/unit', + 'tests/', *session.posargs ) @@ -74,7 +73,7 @@ def cover(session): This outputs the coverage report aggregating coverage from the unit test runs (not system test runs), and then erases coverage data. """ - session.interpreter = 'python2.7' + session.interpreter = 'python3.6' session.install('coverage', 'pytest-cov') session.run('coverage', 'report', '--show-missing', '--fail-under=100') session.run('coverage', 'erase') diff --git a/trace/tests/unit/test_client.py b/trace/tests/unit/test_client.py index 438b20151ee5..64a3d955d408 100644 --- a/trace/tests/unit/test_client.py +++ b/trace/tests/unit/test_client.py @@ -88,11 +88,31 @@ def test_trace_explicit(self): self.assertEqual(result_trace.project_id, self.project) self.assertEqual(result_trace.trace_id, trace_id) - def test_patch_traces(self): + def test_patch_traces_default(self): from google.cloud.trace._gax import _TraceAPI - def patch_traces(project_id, traces, options=None): - _patch_traces_called_with = (project_id, traces, options) + def patch_traces(traces, project_id=None, options=None): + _patch_traces_called_with = (traces, project_id, options) + return _patch_traces_called_with + + credentials = _make_credentials() + client = self._make_one(project=self.project, credentials=credentials) + traces = 'fake_traces_for_test' + + mock_trace_api = mock.Mock(spec=_TraceAPI) + mock_trace_api.patch_traces = patch_traces + patch = mock.patch('google.cloud.trace.client.make_gax_trace_api', return_value=mock_trace_api) + + with patch: + patch_traces_called_with = client.patch_traces(traces=traces) + + self.assertEqual(patch_traces_called_with, (traces, self.project, None)) + + def test_patch_traces_explicit(self): + from google.cloud.trace._gax import _TraceAPI + + def patch_traces(traces, project_id=None, options=None): + _patch_traces_called_with = (traces, project_id, options) return _patch_traces_called_with credentials = _make_credentials() @@ -108,9 +128,31 @@ def patch_traces(project_id, traces, options=None): project_id=self.project, traces=traces) - self.assertEqual(patch_traces_called_with, (self.project, traces, None)) + self.assertEqual(patch_traces_called_with, (traces, self.project, None)) - def test_get_trace(self): + def test_get_trace_default(self): + from google.cloud.trace._gax import _TraceAPI + + def get_trace(trace_id, project_id=None, options=None): + _get_trace_called_with = (trace_id, project_id, options) + return _get_trace_called_with + + credentials = _make_credentials() + client = self._make_one(project=self.project, credentials=credentials) + trace_id = '5e6e73b4131303cb6f5c9dfbaf104e33' + + mock_trace_api = mock.Mock(spec=_TraceAPI) + mock_trace_api.get_trace = get_trace + patch = mock.patch('google.cloud.trace.client.make_gax_trace_api', + return_value=mock_trace_api) + + with patch: + get_trace_called_with = client.get_trace(trace_id=trace_id) + + self.assertEqual(get_trace_called_with, + (trace_id, self.project, None)) + + def test_get_trace_explicit(self): from google.cloud.trace._gax import _TraceAPI def get_trace(trace_id, project_id=None, options=None): @@ -134,7 +176,7 @@ def get_trace(trace_id, project_id=None, options=None): self.assertEqual(get_trace_called_with, (trace_id, self.project, None)) - def test_list_traces(self): + def test_list_traces_default(self): from google.cloud.trace._gax import _TraceAPI def list_traces( @@ -166,9 +208,73 @@ def list_traces( return_value=mock_trace_api) with patch: - list_traces_called_with = client.list_traces( - project_id=self.project) + list_traces_called_with = client.list_traces() self.assertEqual(list_traces_called_with, ( self.project, None, None, None, None, None, None, None)) + + def test_list_traces_explicit(self): + from google.cloud._helpers import _datetime_to_pb_timestamp + from google.cloud.gapic.trace.v1.enums import ListTracesRequest as Enum + from google.cloud.trace._gax import _TraceAPI + + from datetime import datetime + + def list_traces( + project_id, + view=None, + page_size=None, + start_time=None, + end_time=None, + filter_=None, + order_by=None, + page_token=None): + _list_traces_called_with = ( + project_id, + view, + page_size, + start_time, + end_time, + filter_, + order_by, + page_token) + return _list_traces_called_with + + credentials = _make_credentials() + client = self._make_one(project=self.project, credentials=credentials) + + mock_trace_api = mock.Mock(spec=_TraceAPI) + mock_trace_api.list_traces = list_traces + patch = mock.patch('google.cloud.trace.client.make_gax_trace_api', + return_value=mock_trace_api) + + view = Enum.ViewType.COMPLETE + page_size = 10 + start_time = datetime.utcnow() + end_time = datetime.utcnow() + filter_ = '+span:span1' + order_by = 'traceId' + page_token = 'TOKEN' + + + with patch: + list_traces_called_with = client.list_traces( + project_id=self.project, + view=view, + page_size=page_size, + start_time=start_time, + end_time=end_time, + filter_=filter_, + order_by=order_by, + page_token=page_token) + + self.assertEqual(list_traces_called_with, ( + self.project, + view, + page_size, + _datetime_to_pb_timestamp(start_time), + _datetime_to_pb_timestamp(end_time), + filter_, + order_by, + page_token)) From 6436717d48e78432d6ec25295ed4190d47c75869 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Mon, 26 Jun 2017 11:55:37 -0700 Subject: [PATCH 11/16] Complete unit test for trace.py --- trace/google/cloud/trace/trace.py | 6 +- trace/tests/unit/test_trace.py | 167 ++++++++++++++++++++++++++++-- 2 files changed, 163 insertions(+), 10 deletions(-) diff --git a/trace/google/cloud/trace/trace.py b/trace/google/cloud/trace/trace.py index c43d8be7533a..62d5d091a861 100644 --- a/trace/google/cloud/trace/trace.py +++ b/trace/google/cloud/trace/trace.py @@ -93,7 +93,7 @@ def send(self): """ spans_list = [] for root_span in self.spans: - span_tree = traverse_span_tree(root_span) + span_tree = _traverse_span_tree(root_span) spans_list.extend(span_tree) if len(spans_list) == 0: @@ -114,8 +114,10 @@ def send(self): traces=traces, options=None) + return traces -def traverse_span_tree(root_span): + +def _traverse_span_tree(root_span): """Helper to traverse the span tree in level order. :rtype: :class:`~google.cloud.trace.trace_span.TraceSpan` diff --git a/trace/tests/unit/test_trace.py b/trace/tests/unit/test_trace.py index 89e5d1d62a05..b0e85cfd5ecf 100644 --- a/trace/tests/unit/test_trace.py +++ b/trace/tests/unit/test_trace.py @@ -65,8 +65,61 @@ def test_start(self): self.assertEqual(trace.spans, []) - def test_finish(self): - pass + def test_finish_with_empty_span(self): + + def patch_traces(traces, project_id=None, options=None): + _patch_traces_called_with = (traces, project_id, options) + return _patch_traces_called_with + + client = mock.Mock(project=self.project, spec=['project']) + client.patch_traces = patch_traces + trace = self._make_one(client=client) + + with trace: + trace.spans = [None] + self.assertEqual(trace.spans, [None]) + + self.assertEqual(trace.spans, []) + + def test_finish_with_valid_span(self): + from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum + + def patch_traces(traces, project_id=None, options=None): + _patch_traces_called_with = (traces, project_id, options) + return _patch_traces_called_with + + client = mock.Mock(project=self.project, spec=['project']) + client.patch_traces = patch_traces + trace = self._make_one(client=client) + + span_name = 'span' + span_id = 123 + kind = Enum.SpanKind.SPAN_KIND_UNSPECIFIED + start_time = '2017-06-25' + end_time = '2017-06-26' + + span = mock.Mock(name=span_name, + kind=kind, + parent_span_id=None, + span_id=span_id, + start_time=start_time, + end_time=end_time, + labels=None, + children=[], + spec=['name', + 'kind', + 'parent_span_id', + 'span_id', + 'start_time', + 'end_time', + 'labels', + 'children']) + + with trace: + trace.spans = [span] + self.assertEqual(trace.spans, [span]) + + self.assertEqual(trace.spans, []) def test_span(self): from google.cloud.trace.trace_span import TraceSpan @@ -84,13 +137,111 @@ def test_span(self): self.assertIsInstance(result_span, TraceSpan) self.assertEqual(result_span.name, span_name) - def test_send(self): - pass + def test_send_without_spans(self): + def patch_traces(traces, project_id=None, options=None): + self._patch_traces_called_with = (traces, project_id, options) + return self._patch_traces_called_with + client = mock.Mock(project=self.project, spec=['project']) + trace_id = 'test_trace_id' + trace = self._make_one(client=client, trace_id=trace_id) + trace.spans = [] + + client.patch_traces = patch_traces + trace.send() + + self.assertFalse(client.called) + self.assertEqual(trace.project_id, self.project) + self.assertEqual(trace.trace_id, trace_id) + self.assertEqual(trace.spans, []) -class Test_traverse_span_tree(unittest.TestCase): - pass + def test_send_with_spans(self): + from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum + def patch_traces(traces, project_id=None, options=None): + self._patch_traces_called_with = (traces, project_id, options) + return self._patch_traces_called_with -class Test_generate_span_id(unittest.TestCase): - pass + client = mock.Mock(project=self.project, spec=['project']) + trace_id = 'test_trace_id' + trace = self._make_one(client=client, trace_id=trace_id) + child_span_name = 'child_span' + root_span_name = 'root_span' + child_span_id = 123 + root_span_id = 456 + kind = Enum.SpanKind.SPAN_KIND_UNSPECIFIED + start_time = '2017-06-25' + end_time = '2017-06-26' + + child_span = mock.Mock(name=child_span_name, + kind=kind, + parent_span_id=root_span_id, + span_id=child_span_id, + start_time=start_time, + end_time=end_time, + labels=None, + children=[], + spec=['name', + 'kind', + 'parent_span_id', + 'span_id', + 'start_time', + 'end_time', + 'labels', + 'children']) + root_span = mock.Mock(name=root_span_name, + kind=kind, + parent_span_id=None, + span_id=root_span_id, + start_time=start_time, + end_time=end_time, + labels=None, + children=[child_span], + spec=['name', + 'kind', + 'parent_span_id', + 'span_id', + 'start_time', + 'end_time', + 'labels', + 'children']) + + child_span_json = { + 'name': child_span.name, + 'kind': kind, + 'parentSpanId': root_span_id, + 'spanId': child_span_id, + 'startTime': start_time, + 'endTime': end_time, + } + + root_span_json = { + 'name': root_span.name, + 'kind': kind, + 'spanId': root_span_id, + 'startTime': start_time, + 'endTime': end_time, + } + + trace.spans = [root_span] + traces = { + 'traces': [ + { + 'projectId': self.project, + 'traceId': trace_id, + 'spans': [ + root_span_json, + child_span_json + ] + } + ] + } + + client.patch_traces = patch_traces + trace.send() + + self.assertEqual(self._patch_traces_called_with, + (traces, self.project, None)) + self.assertEqual(trace.project_id, self.project) + self.assertEqual(trace.trace_id, trace_id) + self.assertEqual(trace.spans, [root_span]) From 9434b75479ea1c21233f55a7e399d9e4e41f0316 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Mon, 26 Jun 2017 13:29:41 -0700 Subject: [PATCH 12/16] Complete unit test for trace_span.py --- trace/google/cloud/trace/_gax.py | 17 ++++++- trace/google/cloud/trace/_helper.py | 65 -------------------------- trace/google/cloud/trace/trace.py | 5 +- trace/google/cloud/trace/trace_span.py | 2 +- trace/tests/unit/test_trace.py | 7 ++- trace/tests/unit/test_trace_span.py | 47 +++++++++++++++---- 6 files changed, 63 insertions(+), 80 deletions(-) delete mode 100644 trace/google/cloud/trace/_helper.py diff --git a/trace/google/cloud/trace/_gax.py b/trace/google/cloud/trace/_gax.py index 8e9795b4a286..e8c779ccb6c5 100644 --- a/trace/google/cloud/trace/_gax.py +++ b/trace/google/cloud/trace/_gax.py @@ -15,13 +15,14 @@ """GAX Wrapper for interacting with the Stackdriver Trace API.""" from google.cloud.gapic.trace.v1 import trace_service_client -from google.cloud.trace._helper import _traces_mapping_to_pb +from google.cloud.proto.devtools.cloudtrace.v1 import trace_pb2 from google.gax import CallOptions from google.gax import INITIAL_PAGE from google.cloud._helpers import make_secure_channel from google.cloud._http import DEFAULT_USER_AGENT from google.cloud.iterator import GAXIterator from google.protobuf.json_format import MessageToDict +from google.protobuf.json_format import ParseDict class _TraceAPI(object): @@ -196,3 +197,17 @@ def make_gax_trace_api(client): channel=channel, lib_name='gccl') return _TraceAPI(generated, client) + + +def _traces_mapping_to_pb(traces_mapping): + """Convert a trace dict to protobuf. + + :type traces_mapping: dict + :param traces_mapping: A trace mapping. + + :rtype: class:`google.cloud.proto.devtools.cloudtrace.v1.trace_pb2.Traces` + :return: The converted protobuf type traces. + """ + traces_pb = trace_pb2.Traces() + ParseDict(traces_mapping, traces_pb) + return traces_pb diff --git a/trace/google/cloud/trace/_helper.py b/trace/google/cloud/trace/_helper.py deleted file mode 100644 index ca32faed1dcc..000000000000 --- a/trace/google/cloud/trace/_helper.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright 2017 Google 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. - -from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( - TraceSpan, Trace, Traces) -from google.protobuf.json_format import ParseDict - - -def _trace_mapping_to_pb(trace_mapping): - """Helper for converting trace mapping to pb, - Performs "impedance matching" between the protobuf attrs and - the keys expected in the JSON API. - - :type trace_mapping: dict - :param trace_mapping: The trace dict. - - :rtype: :class: `~google.cloud.proto.devtools.cloudtrace.v1. - trace_pb2.Trace` - :returns: The Trace protobuf instance. - """ - trace_pb = Trace() - ParseDict(trace_mapping, trace_pb) - return trace_pb - - -def _span_mapping_to_pb(span_mapping): - """Helper for converting span mapping to pb, - Performs "impedance matching" between the protobuf attrs and - the keys expected in the JSON API. - - :type span_mapping: dict - :param span_mapping: The span dict. - - :rtype: :class: `~google.cloud.proto.devtools.cloudtrace.v1. - trace_pb2.TraceSpan` - :returns: The TraceSpan protobuf instance. - """ - span_pb = TraceSpan() - ParseDict(span_mapping, span_pb) - return span_pb - - -def _traces_mapping_to_pb(traces_mapping): - """Convert a trace dict to protobuf. - - :type traces_mapping: dict - :param traces_mapping: A trace mapping. - - :rtype: class:`google.cloud.proto.devtools.cloudtrace.v1.trace_pb2.Traces` - :return: The converted protobuf type traces. - """ - traces_pb = Traces() - ParseDict(traces_mapping, traces_pb) - return traces_pb diff --git a/trace/google/cloud/trace/trace.py b/trace/google/cloud/trace/trace.py index 62d5d091a861..f520ef1b1c50 100644 --- a/trace/google/cloud/trace/trace.py +++ b/trace/google/cloud/trace/trace.py @@ -23,9 +23,8 @@ class Trace(object): """A trace describes how long it takes for an application to perform an operation. It consists of a set of spans, each of which represent - a single timed event within the operation. - - Node that Trace is not thread-safe and must not be shared between threads. + a single timed event within the operation. Node that Trace is not + thread-safe and must not be shared between threads. See https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools. diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index c9b0bde276b7..83eea4446541 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -127,7 +127,7 @@ def generate_span_id(): :returns: Identifier for the span. Must be a 64-bit integer other than 0 and unique within a trace. Converted to string. """ - span_id = uuid.uuid4().int & (1<<64)-1 + span_id = uuid.uuid4().int & (1 << 64)-1 return int(span_id) diff --git a/trace/tests/unit/test_trace.py b/trace/tests/unit/test_trace.py index b0e85cfd5ecf..7cb7e8cbf044 100644 --- a/trace/tests/unit/test_trace.py +++ b/trace/tests/unit/test_trace.py @@ -172,6 +172,10 @@ def patch_traces(traces, project_id=None, options=None): kind = Enum.SpanKind.SPAN_KIND_UNSPECIFIED start_time = '2017-06-25' end_time = '2017-06-26' + labels = { + '/http/status_code': '200', + '/component': 'HTTP load balancer', + } child_span = mock.Mock(name=child_span_name, kind=kind, @@ -179,7 +183,7 @@ def patch_traces(traces, project_id=None, options=None): span_id=child_span_id, start_time=start_time, end_time=end_time, - labels=None, + labels=labels, children=[], spec=['name', 'kind', @@ -213,6 +217,7 @@ def patch_traces(traces, project_id=None, options=None): 'spanId': child_span_id, 'startTime': start_time, 'endTime': end_time, + 'labels': labels, } root_span_json = { diff --git a/trace/tests/unit/test_trace_span.py b/trace/tests/unit/test_trace_span.py index 899b8f6521b4..7c259a313500 100644 --- a/trace/tests/unit/test_trace_span.py +++ b/trace/tests/unit/test_trace_span.py @@ -87,18 +87,47 @@ def test_constructor_explicit(self): self.assertEqual(span.children, []) def test_span(self): - pass + from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum - def test_set_start_time(self): - pass + span_id = 'test_span_id' + root_span_name = 'root_span' + child_span_name = 'child_span' + root_span = self._make_one(root_span_name) + root_span._child_spans = [] + kind = Enum.SpanKind.SPAN_KIND_UNSPECIFIED - def test_set_end_time(self): - pass + patch = mock.patch( + 'google.cloud.trace.trace_span.generate_span_id', + return_value=span_id) + + with patch: + with root_span: + root_span.span(child_span_name) + + self.assertEqual(len(root_span._child_spans), 1) + + result_child_span = root_span._child_spans[0] + + self.assertEqual(result_child_span.name, child_span_name) + self.assertEqual(result_child_span.span_id, span_id) + self.assertEqual(result_child_span.kind, kind) + self.assertEqual(result_child_span.parent_span_id, root_span.span_id) + self.assertIsNone(result_child_span.labels) + self.assertIsNone(result_child_span.start_time) + self.assertIsNone(result_child_span.end_time) + def test_set_start_time(self): + span_name = 'root_span' + span = self._make_one(span_name) + self.assertIsNone(span.start_time) -class Test_generate_span_id(unittest.TestCase): - pass + span.set_start_time() + self.assertIsNotNone(span.start_time) + def test_set_end_time(self): + span_name = 'root_span' + span = self._make_one(span_name) + self.assertIsNone(span.end_time) -class Test_format_span_json(unittest.TestCase): - pass + span.set_end_time() + self.assertIsNotNone(span.end_time) From 46171d400c3dcc3845ee04a3f7074b9bc9c06811 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Mon, 26 Jun 2017 13:57:38 -0700 Subject: [PATCH 13/16] Remove unused function --- trace/tests/unit/test__gax.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/trace/tests/unit/test__gax.py b/trace/tests/unit/test__gax.py index 86899a99089e..e51dc2bcd92b 100644 --- a/trace/tests/unit/test__gax.py +++ b/trace/tests/unit/test__gax.py @@ -468,9 +468,3 @@ def list_traces( order_by, options) return self._list_traces_response - - -def _datetime_to_rfc3339_w_nanos(value): - from google.cloud._helpers import _RFC3339_NO_FRACTION - no_fraction = value.strftime(_RFC3339_NO_FRACTION) - return '%s.%09dZ' % (no_fraction, value.microsecond * 1000) From 709b7ddb66d0c3be2d1c1646c408bfd4ab70b5c2 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Tue, 27 Jun 2017 11:36:22 -0700 Subject: [PATCH 14/16] Modify tests --- trace/MANIFEST.in | 2 +- trace/google/cloud/trace/_gax.py | 2 +- trace/google/cloud/trace/trace.py | 2 +- trace/google/cloud/trace/trace_span.py | 8 +- trace/tests/unit/test__gax.py | 129 +++++++++---------------- trace/tests/unit/test_trace.py | 20 ++-- 6 files changed, 58 insertions(+), 105 deletions(-) diff --git a/trace/MANIFEST.in b/trace/MANIFEST.in index fc33e282e3be..8a2c2aa5ab99 100644 --- a/trace/MANIFEST.in +++ b/trace/MANIFEST.in @@ -1,5 +1,5 @@ include README.rst LICENSE -recursive-include unit_tests * +recursive-include tests * global-include google *.json *.proto graft google global-exclude *.py[co] diff --git a/trace/google/cloud/trace/_gax.py b/trace/google/cloud/trace/_gax.py index e8c779ccb6c5..c5c61cfd7c0b 100644 --- a/trace/google/cloud/trace/_gax.py +++ b/trace/google/cloud/trace/_gax.py @@ -120,7 +120,7 @@ def list_traces( during which the trace data was collected from the application. - :type filter: str + :type filter_: str :param filter_: (Optional) An optional filter for the request. :type order_by: str diff --git a/trace/google/cloud/trace/trace.py b/trace/google/cloud/trace/trace.py index f520ef1b1c50..23570991fa1f 100644 --- a/trace/google/cloud/trace/trace.py +++ b/trace/google/cloud/trace/trace.py @@ -149,5 +149,5 @@ def generate_trace_id(): :rtype: str :returns: 32 digits randomly generated trace ID. """ - trace_id = str(uuid.uuid4()).replace('-', '') + trace_id = uuid.uuid4().hex return trace_id diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index 83eea4446541..a55c3e50437f 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -17,7 +17,7 @@ from datetime import datetime from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum -import uuid +import random class TraceSpan(object): @@ -125,10 +125,10 @@ def generate_span_id(): :rtype: int :returns: Identifier for the span. Must be a 64-bit integer other - than 0 and unique within a trace. Converted to string. + than 0 and unique within a trace. """ - span_id = uuid.uuid4().int & (1 << 64)-1 - return int(span_id) + span_id = random.getrandbits(64) + return span_id def format_span_json(span): diff --git a/trace/tests/unit/test__gax.py b/trace/tests/unit/test__gax.py index e51dc2bcd92b..3f950021b85e 100644 --- a/trace/tests/unit/test__gax.py +++ b/trace/tests/unit/test__gax.py @@ -35,15 +35,17 @@ def _get_target_class(): return _TraceAPI def test_constructor(self): - gax_api = _GAXTraceAPI() + gax_api = object() client = object() api = self._make_one(gax_api, client) self.assertIs(api._gax_api, gax_api) self.assertIs(api.client, client) def test_patch_traces(self): + from google.cloud.gapic.trace.v1 import trace_service_client from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( TraceSpan, Trace, Traces) + from google.cloud.trace._gax import _traces_mapping_to_pb from google.cloud._helpers import _datetime_to_pb_timestamp from datetime import datetime @@ -71,11 +73,17 @@ def test_patch_traces(self): ], } - gax_api = _GAXTraceAPI() + traces_pb = _traces_mapping_to_pb(traces) + + gax_api = mock.Mock(spec=trace_service_client.TraceServiceClient) api = self._make_one(gax_api, None) api.patch_traces(project_id=self.project, traces=traces) - project_id_called, traces_called, options_called = (gax_api._patch_traces_called_with) + gax_api.patch_traces.assert_called_with(self.project, traces_pb, None) + + call_args = gax_api.patch_traces.call_args[0] + self.assertEqual(len(call_args), 3) + traces_called = call_args[1] self.assertEqual(len(traces_called.traces), 1) trace = traces_called.traces[0] @@ -97,24 +105,20 @@ def test_patch_traces(self): _datetime_to_pb_timestamp(end_time)) self.assertIsInstance(span, TraceSpan) - self.assertIsNone(options_called) - def test_get_trace(self): - from google.cloud.proto.devtools.cloudtrace.v1.trace_pb2 import ( - Trace) + from google.cloud.gapic.trace.v1 import trace_service_client trace_id = 'test_trace_id' - trace_pb = Trace(project_id=self.project, trace_id=trace_id) - gax_api = _GAXTraceAPI(_get_trace_response=trace_pb) + gax_api = mock.Mock(spec=trace_service_client.TraceServiceClient) api = self._make_one(gax_api, None) + patch = mock.patch('google.cloud.trace._gax._parse_trace_pb', + return_value='fake_pb_result') - api.get_trace(project_id=self.project, trace_id=trace_id) + with patch: + api.get_trace(project_id=self.project, trace_id=trace_id) - project_id_called, trace_id_called, options_called = gax_api._get_traces_called_with - self.assertEqual(project_id_called, self.project) - self.assertEqual(trace_id_called, trace_id) - self.assertIsNone(options_called) + gax_api.get_trace.assert_called_with(self.project, trace_id, None) def _make_trace_pb( self, @@ -156,6 +160,7 @@ def _make_trace_pb( def test_list_traces_no_paging(self): from google.cloud._testing import _GAXPageIterator + from google.cloud.gapic.trace.v1 import trace_service_client from google.cloud.gapic.trace.v1.enums import ListTracesRequest as Enum from google.gax import INITIAL_PAGE @@ -186,7 +191,8 @@ def test_list_traces_no_paging(self): labels) response = _GAXPageIterator(trace_pb) - gax_api = _GAXTraceAPI(_list_traces_response=response) + gax_api = mock.Mock(spec=trace_service_client.TraceServiceClient) + gax_api.list_traces.return_value = response api = self._make_one(gax_api, None) iterator = api.list_traces( @@ -216,30 +222,21 @@ def test_list_traces_no_paging(self): self.assertEqual(span['parentSpanId'], str(parent_span_id)) self.assertEqual(span['labels'], labels) - project_id_called,\ - view_called,\ - page_size_called,\ - start_time_called,\ - end_time_called,\ - filter__called,\ - order_by_called,\ - options_called = ( - gax_api._list_traces_called_with - ) - - self.assertEqual(project_id_called, self.project) - self.assertEqual(view_called, view_type) - self.assertEqual(page_size_called, size) - self.assertIsNone(start_time_called) - self.assertIsNone(end_time_called) - self.assertIsNone(filter__called) - self.assertIsNone(order_by_called) - self.assertEqual(options_called.page_token, INITIAL_PAGE) + call_args = gax_api.list_traces.call_args[1] + + self.assertEqual(call_args['project_id'], self.project) + self.assertEqual(call_args['view'], view_type) + self.assertEqual(call_args['page_size'], size) + self.assertIsNone(call_args['start_time']) + self.assertIsNone(call_args['end_time']) + self.assertIsNone(call_args['filter_']) + self.assertIsNone(call_args['order_by']) + self.assertEqual(call_args['options'].page_token, INITIAL_PAGE) def test_list_traces_with_paging(self): from google.cloud._testing import _GAXPageIterator + from google.cloud.gapic.trace.v1 import trace_service_client from google.cloud.gapic.trace.v1.enums import ListTracesRequest as Enum - from google.gax import INITIAL_PAGE from datetime import datetime @@ -269,7 +266,8 @@ def test_list_traces_with_paging(self): labels) response = _GAXPageIterator(trace_pb) - gax_api = _GAXTraceAPI(_list_traces_response=response) + gax_api = mock.Mock(spec=trace_service_client.TraceServiceClient) + gax_api.list_traces.return_value = response api = self._make_one(gax_api, None) iterator = api.list_traces( @@ -300,25 +298,16 @@ def test_list_traces_with_paging(self): self.assertEqual(span['parentSpanId'], str(parent_span_id)) self.assertEqual(span['labels'], labels) - project_id_called,\ - view_called,\ - page_size_called,\ - start_time_called,\ - end_time_called,\ - filter__called,\ - order_by_called,\ - options_called = ( - gax_api._list_traces_called_with - ) - - self.assertEqual(project_id_called, self.project) - self.assertEqual(view_called, view_type) - self.assertEqual(page_size_called, size) - self.assertIsNone(start_time_called) - self.assertIsNone(end_time_called) - self.assertIsNone(filter__called) - self.assertIsNone(order_by_called) - self.assertEqual(options_called.page_token, token) + call_args = gax_api.list_traces.call_args[1] + + self.assertEqual(call_args['project_id'], self.project) + self.assertEqual(call_args['view'], view_type) + self.assertEqual(call_args['page_size'], size) + self.assertIsNone(call_args['start_time']) + self.assertIsNone(call_args['end_time']) + self.assertIsNone(call_args['filter_']) + self.assertIsNone(call_args['order_by']) + self.assertEqual(call_args['options'].page_token, token) class Test__parse_trace_pb(unittest.TestCase): @@ -438,33 +427,3 @@ def generated_api(channel=None, **kwargs): self.assertIsInstance(trace_api, _TraceAPI) self.assertIs(trace_api._gax_api, generated) self.assertIs(trace_api.client, client) - - -class _GAXTraceAPI(_GAXBaseAPI): - def patch_traces(self, project_id, traces, options=None): - self._patch_traces_called_with = (project_id, traces, options) - - def get_trace(self, project_id, trace_id, options=None): - self._get_traces_called_with = (project_id, trace_id, options) - return self._get_trace_response - - def list_traces( - self, - project_id, - view=None, - page_size=None, - start_time=None, - end_time=None, - filter_=None, - order_by=None, - options=None): - self._list_traces_called_with = ( - project_id, - view, - page_size, - start_time, - end_time, - filter_, - order_by, - options) - return self._list_traces_response diff --git a/trace/tests/unit/test_trace.py b/trace/tests/unit/test_trace.py index 7cb7e8cbf044..fc8ed4883fad 100644 --- a/trace/tests/unit/test_trace.py +++ b/trace/tests/unit/test_trace.py @@ -138,16 +138,11 @@ def test_span(self): self.assertEqual(result_span.name, span_name) def test_send_without_spans(self): - def patch_traces(traces, project_id=None, options=None): - self._patch_traces_called_with = (traces, project_id, options) - return self._patch_traces_called_with - client = mock.Mock(project=self.project, spec=['project']) trace_id = 'test_trace_id' trace = self._make_one(client=client, trace_id=trace_id) trace.spans = [] - client.patch_traces = patch_traces trace.send() self.assertFalse(client.called) @@ -157,12 +152,10 @@ def patch_traces(traces, project_id=None, options=None): def test_send_with_spans(self): from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum + from google.cloud.trace.client import Client - def patch_traces(traces, project_id=None, options=None): - self._patch_traces_called_with = (traces, project_id, options) - return self._patch_traces_called_with - - client = mock.Mock(project=self.project, spec=['project']) + client = mock.Mock(spec=Client) + client.project = self.project trace_id = 'test_trace_id' trace = self._make_one(client=client, trace_id=trace_id) child_span_name = 'child_span' @@ -242,11 +235,12 @@ def patch_traces(traces, project_id=None, options=None): ] } - client.patch_traces = patch_traces trace.send() - self.assertEqual(self._patch_traces_called_with, - (traces, self.project, None)) + client.patch_traces.assert_called_with(project_id=self.project, + traces=traces, + options=None) + self.assertEqual(trace.project_id, self.project) self.assertEqual(trace.trace_id, trace_id) self.assertEqual(trace.spans, [root_span]) From 9162cf6fc8cdc0935b09e09ce1801d977ab1aa48 Mon Sep 17 00:00:00 2001 From: Angela Li Date: Tue, 27 Jun 2017 15:39:43 -0700 Subject: [PATCH 15/16] Change to use itertools to traverse spans --- trace/google/cloud/trace/trace.py | 34 ++------- trace/google/cloud/trace/trace_span.py | 6 ++ trace/tests/unit/test_trace.py | 98 +++++++++----------------- trace/tests/unit/test_trace_span.py | 20 ++++++ 4 files changed, 64 insertions(+), 94 deletions(-) diff --git a/trace/google/cloud/trace/trace.py b/trace/google/cloud/trace/trace.py index 23570991fa1f..21414cf50e50 100644 --- a/trace/google/cloud/trace/trace.py +++ b/trace/google/cloud/trace/trace.py @@ -92,8 +92,9 @@ def send(self): """ spans_list = [] for root_span in self.spans: - span_tree = _traverse_span_tree(root_span) - spans_list.extend(span_tree) + span_tree = list(iter(root_span)) + span_tree_json = [format_span_json(span) for span in span_tree] + spans_list.extend(span_tree_json) if len(spans_list) == 0: return @@ -113,34 +114,7 @@ def send(self): traces=traces, options=None) - return traces - - -def _traverse_span_tree(root_span): - """Helper to traverse the span tree in level order. - - :rtype: :class:`~google.cloud.trace.trace_span.TraceSpan` - :param root_span: The root span in a span tree. - - :rtype: list - :returns: A list of all the spans in a span tree. - """ - span_list = [] - - if root_span is None: - return span_list - - span_queue = [] - span_queue.append(root_span) - - while span_queue: - cur_span = span_queue.pop(0) - span_list.append(format_span_json(cur_span)) - - for child_span in cur_span.children: - span_queue.append(child_span) - - return span_list + return spans_list def generate_trace_id(): diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index a55c3e50437f..a692c8942835 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -17,6 +17,7 @@ from datetime import datetime from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum +from itertools import chain import random @@ -112,6 +113,11 @@ def set_end_time(self): """Set the end time for a span.""" self.end_time = datetime.utcnow().isoformat() + 'Z' + def __iter__(self): + for span in chain(*(map(iter, self.children))): + yield span + yield self + def __enter__(self): self.set_start_time() return self diff --git a/trace/tests/unit/test_trace.py b/trace/tests/unit/test_trace.py index fc8ed4883fad..3b50f1563174 100644 --- a/trace/tests/unit/test_trace.py +++ b/trace/tests/unit/test_trace.py @@ -65,24 +65,9 @@ def test_start(self): self.assertEqual(trace.spans, []) - def test_finish_with_empty_span(self): - - def patch_traces(traces, project_id=None, options=None): - _patch_traces_called_with = (traces, project_id, options) - return _patch_traces_called_with - - client = mock.Mock(project=self.project, spec=['project']) - client.patch_traces = patch_traces - trace = self._make_one(client=client) - - with trace: - trace.spans = [None] - self.assertEqual(trace.spans, [None]) - - self.assertEqual(trace.spans, []) - def test_finish_with_valid_span(self): from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum + from google.cloud.trace.trace_span import TraceSpan def patch_traces(traces, project_id=None, options=None): _patch_traces_called_with = (traces, project_id, options) @@ -98,22 +83,16 @@ def patch_traces(traces, project_id=None, options=None): start_time = '2017-06-25' end_time = '2017-06-26' - span = mock.Mock(name=span_name, - kind=kind, - parent_span_id=None, - span_id=span_id, - start_time=start_time, - end_time=end_time, - labels=None, - children=[], - spec=['name', - 'kind', - 'parent_span_id', - 'span_id', - 'start_time', - 'end_time', - 'labels', - 'children']) + span = mock.Mock(spec=TraceSpan) + span.name = span_name + span.kind = kind + span.parent_span_id = None + span.span_id = span_id + span.start_time = start_time + span.end_time = end_time + span.labels = None + span.children = [] + span.__iter__ = mock.Mock(return_value=iter([span])) with trace: trace.spans = [span] @@ -153,6 +132,7 @@ def test_send_without_spans(self): def test_send_with_spans(self): from google.cloud.gapic.trace.v1.enums import TraceSpan as Enum from google.cloud.trace.client import Client + from google.cloud.trace.trace_span import TraceSpan client = mock.Mock(spec=Client) client.project = self.project @@ -170,38 +150,28 @@ def test_send_with_spans(self): '/component': 'HTTP load balancer', } - child_span = mock.Mock(name=child_span_name, - kind=kind, - parent_span_id=root_span_id, - span_id=child_span_id, - start_time=start_time, - end_time=end_time, - labels=labels, - children=[], - spec=['name', - 'kind', - 'parent_span_id', - 'span_id', - 'start_time', - 'end_time', - 'labels', - 'children']) - root_span = mock.Mock(name=root_span_name, - kind=kind, - parent_span_id=None, - span_id=root_span_id, - start_time=start_time, - end_time=end_time, - labels=None, - children=[child_span], - spec=['name', - 'kind', - 'parent_span_id', - 'span_id', - 'start_time', - 'end_time', - 'labels', - 'children']) + child_span = mock.Mock(spec=TraceSpan) + child_span.name = child_span_name + child_span.kind = kind + child_span.parent_span_id = root_span_id + child_span.span_id = child_span_id + child_span.start_time = start_time + child_span.end_time = end_time + child_span.labels = labels + child_span.children = [] + child_span.__iter__ = mock.Mock(return_value=iter([child_span])) + + root_span = mock.Mock(spec=TraceSpan) + root_span.name = root_span_name + root_span.kind = kind + root_span.parent_span_id = None + root_span.span_id = root_span_id + root_span.start_time = start_time + root_span.end_time = end_time + root_span.labels = None + root_span.children = [] + root_span.__iter__ = mock.Mock( + return_value=iter([root_span, child_span])) child_span_json = { 'name': child_span.name, diff --git a/trace/tests/unit/test_trace_span.py b/trace/tests/unit/test_trace_span.py index 7c259a313500..a56a2e3d8063 100644 --- a/trace/tests/unit/test_trace_span.py +++ b/trace/tests/unit/test_trace_span.py @@ -131,3 +131,23 @@ def test_set_end_time(self): span.set_end_time() self.assertIsNotNone(span.end_time) + + def test___iter__(self): + root_span_name = 'root_span_name' + child1_span_name = 'child1_span_name' + child2_span_name = 'child2_span_name' + child1_child1_span_name = 'child1_child1_span_name' + + root_span = self._make_one(root_span_name) + child1_span = self._make_one(child1_span_name) + child2_span = self._make_one(child2_span_name) + child1_child1_span = self._make_one(child1_child1_span_name) + + child1_span._child_spans.append(child1_child1_span) + root_span._child_spans.extend([child1_span, child2_span]) + + span_iter_list = list(iter(root_span)) + + self.assertEqual( + span_iter_list, + [child1_child1_span, child1_span, child2_span, root_span]) From 992c749a70930a70618439ad7ff8376af3f9ccdc Mon Sep 17 00:00:00 2001 From: Angela Li Date: Tue, 27 Jun 2017 16:12:56 -0700 Subject: [PATCH 16/16] Fix style --- trace/google/cloud/trace/_gax.py | 6 +++--- trace/google/cloud/trace/client.py | 8 ++++---- trace/google/cloud/trace/trace.py | 4 +--- trace/google/cloud/trace/trace_span.py | 6 +++--- trace/nox.py | 4 ++-- trace/tests/unit/test_client.py | 26 ++++++++++++-------------- 6 files changed, 25 insertions(+), 29 deletions(-) diff --git a/trace/google/cloud/trace/_gax.py b/trace/google/cloud/trace/_gax.py index c5c61cfd7c0b..b412b54ee856 100644 --- a/trace/google/cloud/trace/_gax.py +++ b/trace/google/cloud/trace/_gax.py @@ -131,7 +131,7 @@ def list_traces( passed, the API will return the first page of entries. - :rtype: dict + :rtype: :class:`~google.cloud.iterator.Iterator` :returns: Traces that match the specified filter conditions. """ if page_token is None: @@ -187,7 +187,7 @@ def make_gax_trace_api(client): :param client: The client that holds configuration details. :rtype: :class:`~google.cloud.trace._gax._TraceAPI` - :return: A Trace API instance with the proper configurations. + :returns: A Trace API instance with the proper configurations. """ channel = make_secure_channel( client._credentials, @@ -206,7 +206,7 @@ def _traces_mapping_to_pb(traces_mapping): :param traces_mapping: A trace mapping. :rtype: class:`google.cloud.proto.devtools.cloudtrace.v1.trace_pb2.Traces` - :return: The converted protobuf type traces. + :returns: The converted protobuf type traces. """ traces_pb = trace_pb2.Traces() ParseDict(traces_mapping, traces_pb) diff --git a/trace/google/cloud/trace/client.py b/trace/google/cloud/trace/client.py index 0195692b5f78..bc6e08cbda4d 100644 --- a/trace/google/cloud/trace/client.py +++ b/trace/google/cloud/trace/client.py @@ -61,7 +61,7 @@ def trace(self, project_id=None, trace_id=None): :param trace_id: ID of the trace. 32 digits uuid. :rtype: :class:`~google.cloud.trace.trace.Trace` - :return: A Trace instance. + :returns: A Trace instance. """ if project_id is None: project_id = self.project @@ -85,7 +85,7 @@ def patch_traces(self, traces, project_id=None, options=None): if project_id is None: project_id = self.project - return self.trace_api.patch_traces( + self.trace_api.patch_traces( project_id=project_id, traces=traces, options=options) @@ -104,7 +104,7 @@ def get_trace(self, trace_id, project_id=None, options=None): :param options: (Optional) Overrides the default settings for this call, e.g, timeout, retries etc. - :rtype: :dict + :rtype: dict :returns: A Trace dict. """ if project_id is None: @@ -163,7 +163,7 @@ def list_traces( passed, the API will return the first page of entries. - :rtype: dict + :rtype: :class:`~google.cloud.iterator.Iterator` :returns: Traces that match the specified filter conditions. """ if project_id is None: diff --git a/trace/google/cloud/trace/trace.py b/trace/google/cloud/trace/trace.py index 21414cf50e50..e726c7361f2f 100644 --- a/trace/google/cloud/trace/trace.py +++ b/trace/google/cloud/trace/trace.py @@ -74,7 +74,7 @@ def span(self, name='span'): """Create a new span for the trace and append it to the spans list. :type name: str - :param name: The name of the span. + :param name: (Optional) The name of the span. :rtype: :class:`~google.cloud.trace.trace_span.TraceSpan` :returns: A TraceSpan to be added to the current Trace. @@ -114,8 +114,6 @@ def send(self): traces=traces, options=None) - return spans_list - def generate_trace_id(): """Generate a trace_id randomly. diff --git a/trace/google/cloud/trace/trace_span.py b/trace/google/cloud/trace/trace_span.py index a692c8942835..3ead9243d5cb 100644 --- a/trace/google/cloud/trace/trace_span.py +++ b/trace/google/cloud/trace/trace_span.py @@ -43,8 +43,8 @@ class TraceSpan(object): distinguished using RPC_CLIENT and RPC_SERVER to identify queueing latency associated with the span. - :type parent_span_id: str - :param parent_span_id: ID of the parent span. Optional. + :type parent_span_id: int + :param parent_span_id: (Optional) ID of the parent span. :type labels: dict :param labels: Collection of labels associated with the span. @@ -60,7 +60,7 @@ class TraceSpan(object): :param end_time: (Optional) End of the time interval (inclusive) during which the trace data was collected from the application. - :type span_id: str + :type span_id: int :param span_id: Identifier for the span, unique within a trace. """ diff --git a/trace/nox.py b/trace/nox.py index 08c69cb85df5..d7376a745130 100644 --- a/trace/nox.py +++ b/trace/nox.py @@ -52,7 +52,7 @@ def lint(session): Returns a failure if flake8 finds linting errors or sufficiently serious code quality issues. """ - session.interpreter = 'python3.6' + session.interpreter = 'python2.7' session.install('flake8', *LOCAL_DEPS) session.install('.') session.run('flake8', 'google/cloud/trace') @@ -73,7 +73,7 @@ def cover(session): This outputs the coverage report aggregating coverage from the unit test runs (not system test runs), and then erases coverage data. """ - session.interpreter = 'python3.6' + session.interpreter = 'python2.7' session.install('coverage', 'pytest-cov') session.run('coverage', 'report', '--show-missing', '--fail-under=100') session.run('coverage', 'erase') diff --git a/trace/tests/unit/test_client.py b/trace/tests/unit/test_client.py index 64a3d955d408..39193cbec69a 100644 --- a/trace/tests/unit/test_client.py +++ b/trace/tests/unit/test_client.py @@ -91,44 +91,42 @@ def test_trace_explicit(self): def test_patch_traces_default(self): from google.cloud.trace._gax import _TraceAPI - def patch_traces(traces, project_id=None, options=None): - _patch_traces_called_with = (traces, project_id, options) - return _patch_traces_called_with - credentials = _make_credentials() client = self._make_one(project=self.project, credentials=credentials) traces = 'fake_traces_for_test' mock_trace_api = mock.Mock(spec=_TraceAPI) - mock_trace_api.patch_traces = patch_traces + mock_trace_api.patch_traces = mock.Mock() patch = mock.patch('google.cloud.trace.client.make_gax_trace_api', return_value=mock_trace_api) with patch: - patch_traces_called_with = client.patch_traces(traces=traces) + client.patch_traces(traces=traces) - self.assertEqual(patch_traces_called_with, (traces, self.project, None)) + mock_trace_api.patch_traces.assert_called_with( + options=None, + project_id='PROJECT', + traces='fake_traces_for_test') def test_patch_traces_explicit(self): from google.cloud.trace._gax import _TraceAPI - def patch_traces(traces, project_id=None, options=None): - _patch_traces_called_with = (traces, project_id, options) - return _patch_traces_called_with - credentials = _make_credentials() client = self._make_one(project=self.project, credentials=credentials) traces = 'fake_traces_for_test' mock_trace_api = mock.Mock(spec=_TraceAPI) - mock_trace_api.patch_traces = patch_traces + mock_trace_api.patch_traces = mock.Mock() patch = mock.patch('google.cloud.trace.client.make_gax_trace_api', return_value=mock_trace_api) with patch: - patch_traces_called_with = client.patch_traces( + client.patch_traces( project_id=self.project, traces=traces) - self.assertEqual(patch_traces_called_with, (traces, self.project, None)) + mock_trace_api.patch_traces.assert_called_with( + options=None, + project_id='PROJECT', + traces='fake_traces_for_test') def test_get_trace_default(self): from google.cloud.trace._gax import _TraceAPI