testutils

package
v0.0.0-...-5af360f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 20, 2024 License: Apache-2.0 Imports: 56 Imported by: 0

README

Tests Utilities

The testutils package provides an internal test format for Collector data, and helpers to help assert its integrity from arbitrary components.

Resource Metrics

ResourceMetrics are at the core of the internal metric data format for these tests and are intended to be defined in yaml files or by converting from obtained pdata.Metrics items. They provide a strict Equals() helper method as well as RelaxedEquals() to help in verifying desired field and values, ignoring those not specified.

resource_metrics:
  - attributes:
      a_resource_attribute: a_value
      another_resource_attribute: another_value
    scope_metrics:
      - instrumentation_scope:
          name: a library
          version: some version
      - metrics:
          - name: my.int.gauge
            type: IntGauge
            description: my.int.gauge description
            unit: ms
            attributes:
              my_attribute: my_attribute_value
              my_other_attribute: my_other_attribute_value
            value: 123
          - name: my.double.sum
            type: DoubleNonmonotonicDeltaSum
            attributes: {} # enforced empty attribute map in RelaxedEquals() (only point with no attributes matches)
            value: -123.456
  - scope_metrics:
      - instrumentation_scope:
          name: an instrumentation library from a different resource without attributes
        metrics:
          - name: my.double.gauge
            type: DoubleGauge
            # missing attributes field, so values are not compared in RelaxedEquals() (any attribute values are accepted)
            value: 456.789
          - name: my.double.gauge
            type: DoubleGauge
            attributes:
              another: attribute
            value: 567.890
      - instrumentation_scope:
          name: another instrumentation library
          version: this_library_version
        metrics:
          - name: another_int_gauge
            type: IntGauge
            value: 456

Using telemetry.LoadResourceMetrics("my_yaml.path") you can create an equivalent ResourceMetrics instance to what your yaml file specifies. Using telemetry.PDataToResourceMetrics(myReceivedPDataMetrics) you can use the assertion helpers to determine if your expected ResourceMetrics are the same as those received in your test case. telemetry.FlattenResourceMetrics() is a good way to "normalize" metrics received over time to ensure that only unique datapoints are represented, and that all unique Resources and Instrumentation Libraries have a single item.

Test Containers

The Testcontainers project is a popular testing resource for easy container creation and usage for a number of languages including Go. The testutils package provides a helpful container builder and wrapper library to avoid needing direct Docker api usage:

import "github.com/signafx/splunk-otel-collector/tests/testutils"

myContainerFromImageName := testutils.NewContainer().WithImage(
	"my-docker-image:123.4-alpine",
).WithEnvVar("MY_ENV_VAR", "ENV_VAR_VALUE",
).WithExposedPorts("12345:12345").WillWaitForPorts("12345",
).WillWaitForLogs(
    "My container is running and ready for interaction"
).Build()

// After building, `myContainerFromImageName` implements the testscontainer.Container interface
err := myContainerFromImageName.Start(context.Background())

myContainerFromBuildContext := testutils.NewContainer().WithContext(
    "./directory_with_dockerfile_and_resources",
).WithEnv(map[string]string{
    "MY_ENV_VAR_1": "value1",
    "MY_ENV_VAR_2": "value2",
    "MY_ENV_VAR_3": "value3",
}).WithExposedPorts("23456:23456", "34567:34567",
).WillWaitForPorts("23456", "34567",
).WillWaitForLogs(
    "My container is running.", "My container is ready for interaction"
).Build()

err = myContainerFromBuildContext.Start(context.Background())
OTLP Metrics Receiver Sink

The OTLPReceiverSink is a helper type that will easily stand up an in memory OTLP Receiver with consumertest.MetricsSink functionality. It will listen to the configured gRPC endpoint that running Collector processes can be configured to reach and provides an AssertAllMetricsReceived() test method to confirm that expected ResourceMetrics are received within the specified window.

import "github.com/signafx/splunk-otel-collector/tests/testutils"

otlp, err := testutils.NewOTLPReceiverSink().WithEndpoint("localhost:23456").Build()
require.NoError(t, err)

defer func() {
    require.Nil(t, otlp.Shutdown())
}()

require.NoError(t, otlp.Start())

require.NoError(t, otlp.AssertAllMetricsReceived(t, expectedResourceMetrics, 10*time.Second))
Collector Process

The CollectorProcess is a helper type that will run the desired Collector executable as a subprocess using whatever config you provide. If an executable path isn't specified via builder method, the first bin/otelcol match walking up your current directory tree will be used, which can be helpful to ease test development and execution.

You can also specify the desired command line arguments using builder.WithArgs() if not simply running with the specified config.

import "github.com/signafx/splunk-otel-collector/tests/testutils"

collector, err := testutils.NewCollectorProcess().WithPath("my_otel_collector_path",
).WithConfigPath("my_config_path").WithLogger(logger).WithLogLevel("debug").Build()

err = collector.Start()
require.NoError(t, err)
defer func() { require.NoError(t, collector.Shutdown()) }()

// Also able to specify other arguments for feature, performance, and soak testing.
// path will be first `bin/otelcol` match in a parent directory
collector, err = testutils.NewCollectorProcess().WithArgs("--tested-feature", "--etc").Build()
Collector Container

The CollectorContainer is an equivalent helper type to the CollectorProcess but will run a container in host network mode for an arbitrary Collector image and tag using the config you provide. If an image is not specified it will use a default of "otelcol:latest" (the default local image tag built via make docker-otelcol).

import "github.com/signafx/splunk-otel-collector/tests/testutils"

collector, err := testutils.NewCollectorContainer().WithImage("quay.io/signalfx/splunk-otel-collector:latest",
).WithConfigPath("my_config_path").Build()

err = collector.Start()
require.NoError(t, err)
defer func() { require.NoError(t, collector.Shutdown()) }()
Testcase

All the above test utilities can be easily configured by the Testcase helper to avoid unnecessary boilerplate in resource creation and cleanup. The associated OTLPReceiverSink for each Testcase will have an OS-provided endpoint that can be rendered via the "${OTLP_ENDPOINT}" environment variable in your tested config. testutils provides a general AssertAllMetricsReceived() function that utilizes this type to stand up all the necessary resources associated with a test and assert that all expected metrics are received:

If the SPLUNK_OTEL_COLLECTOR_IMAGE environment variable is set and not empty its value will be used to start a CollectorContainer instead of a subprocess.

import "github.com/signafx/splunk-otel-collector/tests/testutils"

func MyTest(t *testing.T) {
    containers := []testutils.Container{
        testutils.NewContainer().WithImage("my_docker_image"),
        testutils.NewContainer().WithImage("my_other_docker_image"),
    }

    // will implicitly create a Testcase with OTLPReceiverSink listening at $OTLP_ENDPOINT,
    // ./testdata/resource_metrics/my_resource_metrics.yaml ResourceMetrics instance, CollectorProcess with
    // ./testdata/my_collector_config.yaml config, and build and start all specified containers before calling
    // OTLPReceiverSink.AssertAllMetricsReceived() with a 30s wait duration.
    testutils.AssertAllMetricsReceived(t, "my_resource_metrics.yaml", "my_collector_config.yaml", containers, nil)
}

Documentation

Overview

Package testutils provides utilities for verifying the functionality of and content produced by collector components.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertAllMetricsReceived

func AssertAllMetricsReceived(
	t testing.TB, resourceMetricsFilename, collectorConfigFilename string,
	containers []Container, builders []CollectorBuilder,
)

AssertAllMetricsReceived is a central helper, designed to avoid most boilerplate. Using the desired ResourceMetrics and Collector Config filenames, a slice of Container builders, and a slice of CollectorBuilder AssertAllMetricsReceived creates a Testcase, builds and starts all Container and CollectorBuilder-determined Collector instances, and asserts that all expected ResourceMetrics are received before running validated cleanup functionality.

func CollectorImageIsForArm

func CollectorImageIsForArm(t testing.TB) bool

func CollectorImageIsSet

func CollectorImageIsSet() bool

func GetAvailablePort

func GetAvailablePort(t testing.TB) uint16

GetAvailablePort finds an available local port and returns it. The port is available for opening when this function returns provided that there is no race by some other code to grab the same port immediately.

func GetCollectorImage

func GetCollectorImage() string

func GetCollectorImageOrSkipTest

func GetCollectorImageOrSkipTest(t testing.TB) string

func GetDockerGID

func GetDockerGID(t testing.TB) uint32

func RunMetricsCollectionTest

func RunMetricsCollectionTest(t *testing.T, configFile string, expectedFilePath string,
	options ...MetricsCollectionTestOption)

RunMetricsCollectionTest runs a test that collects metrics using a collector container with provided configFile and compares the result with the expected metrics defined in the file expectedFilePath.

func SkipIfNotContainerTest

func SkipIfNotContainerTest(t testing.TB)

Types

type Collector

type Collector interface {
	WithConfigPath(path string) Collector
	WithArgs(args ...string) Collector
	WithEnv(env map[string]string) Collector
	WithLogger(logger *zap.Logger) Collector
	WithLogLevel(level string) Collector
	WillFail(fail bool) Collector
	WithMount(path string, mountPoint string) Collector
	Build() (Collector, error)
	Start() error
	Shutdown() error
	InitialConfig(t testing.TB, port uint16) map[string]any
	EffectiveConfig(t testing.TB, port uint16) map[string]any
}

type CollectorBuilder

type CollectorBuilder func(Collector) Collector

type CollectorContainer

type CollectorContainer struct {
	Logger *zap.Logger

	Mounts     map[string]string
	Image      string
	ConfigPath string
	LogLevel   string
	Args       []string
	Ports      []string
	Container  Container
	Fail       bool
	// contains filtered or unexported fields
}

func NewCollectorContainer

func NewCollectorContainer() CollectorContainer

To be used as a builder whose Build() method provides the actual instance capable of launching the process.

func (CollectorContainer) Build

func (collector CollectorContainer) Build() (Collector, error)

func (*CollectorContainer) EffectiveConfig

func (collector *CollectorContainer) EffectiveConfig(t testing.TB, port uint16) map[string]any

func (*CollectorContainer) InitialConfig

func (collector *CollectorContainer) InitialConfig(t testing.TB, port uint16) map[string]any

func (*CollectorContainer) Shutdown

func (collector *CollectorContainer) Shutdown() error

func (*CollectorContainer) Start

func (collector *CollectorContainer) Start() error

func (CollectorContainer) WillFail

func (collector CollectorContainer) WillFail(fail bool) Collector

func (CollectorContainer) WithArgs

func (collector CollectorContainer) WithArgs(args ...string) Collector

[]string{} by default

func (CollectorContainer) WithConfigPath

func (collector CollectorContainer) WithConfigPath(path string) Collector

Will use bundled config by default

func (CollectorContainer) WithEnv

func (collector CollectorContainer) WithEnv(env map[string]string) Collector

empty by default

func (CollectorContainer) WithExposedPorts

func (collector CollectorContainer) WithExposedPorts(ports ...string) CollectorContainer

func (CollectorContainer) WithImage

func (collector CollectorContainer) WithImage(image string) CollectorContainer

otelcol:latest by default

func (CollectorContainer) WithLogLevel

func (collector CollectorContainer) WithLogLevel(level string) Collector

"info" by default, but currently a noop

func (CollectorContainer) WithLogger

func (collector CollectorContainer) WithLogger(logger *zap.Logger) Collector

Nop logger by default

func (CollectorContainer) WithMount

func (collector CollectorContainer) WithMount(path string, mountPoint string) Collector

type CollectorProcess

type CollectorProcess struct {
	Env     map[string]string
	Logger  *zap.Logger
	Process *subprocess.Subprocess

	Path       string
	ConfigPath string
	LogLevel   string
	Args       []string
	Fail       bool
	// contains filtered or unexported fields
}

func NewCollectorProcess

func NewCollectorProcess() CollectorProcess

To be used as a builder whose Build() method provides the actual instance capable of launching the process.

func (CollectorProcess) Build

func (collector CollectorProcess) Build() (Collector, error)

func (*CollectorProcess) EffectiveConfig

func (collector *CollectorProcess) EffectiveConfig(t testing.TB, port uint16) map[string]any

func (*CollectorProcess) InitialConfig

func (collector *CollectorProcess) InitialConfig(t testing.TB, port uint16) map[string]any

func (*CollectorProcess) Shutdown

func (collector *CollectorProcess) Shutdown() error

func (*CollectorProcess) Start

func (collector *CollectorProcess) Start() error

func (CollectorProcess) WillFail

func (collector CollectorProcess) WillFail(fail bool) Collector

noop at this time

func (CollectorProcess) WithArgs

func (collector CollectorProcess) WithArgs(args ...string) Collector

[]string{"--set=service.telemetry.logs.level={collector.LogLevel}", "--config", collector.ConfigPath, "--set=service.telemetry.metrics.level=none"} by default

func (CollectorProcess) WithConfigPath

func (collector CollectorProcess) WithConfigPath(path string) Collector

Required

func (CollectorProcess) WithEnv

func (collector CollectorProcess) WithEnv(env map[string]string) Collector

empty by default

func (CollectorProcess) WithLogLevel

func (collector CollectorProcess) WithLogLevel(level string) Collector

info by default

func (CollectorProcess) WithLogger

func (collector CollectorProcess) WithLogger(logger *zap.Logger) Collector

Nop logger by default

func (CollectorProcess) WithMount

func (collector CollectorProcess) WithMount(string, string) Collector

func (CollectorProcess) WithPath

func (collector CollectorProcess) WithPath(path string) CollectorProcess

Nearest `bin/otelcol` by default

type Container

type Container struct {
	Env                  map[string]string
	Labels               map[string]string
	Dockerfile           testcontainers.FromDockerfile
	User                 string
	Image                string
	ContainerName        string
	ContainerNetworkMode string
	Entrypoint           []string
	Cmd                  []string
	ContainerNetworks    []string
	ExposedPorts         []string
	Binds                []string
	WaitingFor           []wait.Strategy
	Mounts               []testcontainers.ContainerMount
	Files                []testcontainers.ContainerFile
	HostConfigModifiers  []func(*dockerContainer.HostConfig)
	Privileged           bool
	// contains filtered or unexported fields
}

Container is a combination builder and testcontainers.Container wrapper for convenient creation and management of docker images and containers.

func NewContainer

func NewContainer() Container

To be used as a builder whose Build() method provides the actual instance capable of being started, and that implements a testcontainers.Container.

func (*Container) AssertExec

func (container *Container) AssertExec(t testing.TB, timeout time.Duration, cmd ...string) (rc int, stdout, stderr string)

AssertExec will assert that the exec'ed command completes within the specified timeout, returning the return code and demuxed stdout and stderr

func (Container) Build

func (container Container) Build() *Container

func (*Container) ContainerIP

func (container *Container) ContainerIP(ctx context.Context) (string, error)

func (*Container) ContainerIPs

func (container *Container) ContainerIPs(ctx context.Context) ([]string, error)

func (*Container) CopyDirToContainer

func (container *Container) CopyDirToContainer(ctx context.Context, hostDirPath string, containerParentPath string, fileMode int64) error

func (*Container) CopyFileFromContainer

func (container *Container) CopyFileFromContainer(ctx context.Context, filePath string) (io.ReadCloser, error)

func (*Container) CopyFileToContainer

func (container *Container) CopyFileToContainer(ctx context.Context, hostFilePath string, containerFilePath string, fileMode int64) error

func (*Container) CopyToContainer

func (container *Container) CopyToContainer(ctx context.Context, fileContent []byte, containerFilePath string, fileMode int64) error

func (*Container) Endpoint

func (container *Container) Endpoint(ctx context.Context, s string) (string, error)

func (*Container) Exec

func (container *Container) Exec(ctx context.Context, cmd []string, options ...exec.ProcessOption) (int, io.Reader, error)

func (*Container) FollowOutput

func (container *Container) FollowOutput(consumer testcontainers.LogConsumer)

func (*Container) GetContainerID

func (container *Container) GetContainerID() string

func (*Container) Host

func (container *Container) Host(ctx context.Context) (string, error)

func (*Container) IsRunning

func (container *Container) IsRunning() bool

func (*Container) Logs

func (container *Container) Logs(ctx context.Context) (io.ReadCloser, error)

func (*Container) MappedPort

func (container *Container) MappedPort(ctx context.Context, port nat.Port) (nat.Port, error)

func (*Container) Name

func (container *Container) Name(ctx context.Context) (string, error)

func (*Container) NetworkAliases

func (container *Container) NetworkAliases(ctx context.Context) (map[string][]string, error)

func (*Container) Networks

func (container *Container) Networks(ctx context.Context) ([]string, error)

func (*Container) PortEndpoint

func (container *Container) PortEndpoint(ctx context.Context, port nat.Port, s string) (string, error)

func (*Container) Ports

func (container *Container) Ports(ctx context.Context) (nat.PortMap, error)

func (*Container) SessionID

func (container *Container) SessionID() string

func (*Container) Start

func (container *Container) Start(ctx context.Context) error

func (*Container) StartLogProducer

func (container *Container) StartLogProducer(ctx context.Context) error

func (*Container) State

func (container *Container) State(ctx context.Context) (*types.ContainerState, error)

func (*Container) Stop

func (container *Container) Stop(ctx context.Context, timeout *time.Duration) error

func (*Container) StopLogProducer

func (container *Container) StopLogProducer() error

func (*Container) Terminate

func (container *Container) Terminate(ctx context.Context) error

func (Container) WillWaitForHealth

func (container Container) WillWaitForHealth(waitTime time.Duration) Container

func (Container) WillWaitForLogs

func (container Container) WillWaitForLogs(logStatements ...string) Container

func (Container) WillWaitForPorts

func (container Container) WillWaitForPorts(ports ...string) Container

func (Container) WithBinds

func (container Container) WithBinds(binds ...string) Container

func (Container) WithBuildArgs

func (container Container) WithBuildArgs(args map[string]*string) Container

func (Container) WithCmd

func (container Container) WithCmd(cmd ...string) Container

func (Container) WithContext

func (container Container) WithContext(path string) Container

func (Container) WithContextArchive

func (container Container) WithContextArchive(contextArchive io.ReadSeeker) Container

func (Container) WithDockerfile

func (container Container) WithDockerfile(dockerfile string) Container

func (Container) WithEntrypoint

func (container Container) WithEntrypoint(entrypoint ...string) Container

func (Container) WithEnv

func (container Container) WithEnv(env map[string]string) Container

func (Container) WithEnvVar

func (container Container) WithEnvVar(key, value string) Container

func (Container) WithExposedPorts

func (container Container) WithExposedPorts(ports ...string) Container

func (Container) WithFile

func (container Container) WithFile(file testcontainers.ContainerFile) Container

func (Container) WithHostConfigModifier

func (container Container) WithHostConfigModifier(cm func(*dockerContainer.HostConfig)) Container

func (Container) WithImage

func (container Container) WithImage(image string) Container

func (Container) WithLabel

func (container Container) WithLabel(key, value string) Container

func (Container) WithLabels

func (container Container) WithLabels(labels map[string]string) Container

func (Container) WithMount

func (container Container) WithMount(mount testcontainers.ContainerMount) Container

func (Container) WithName

func (container Container) WithName(name string) Container

func (Container) WithNetworkMode

func (container Container) WithNetworkMode(mode string) Container

func (Container) WithNetworks

func (container Container) WithNetworks(networks ...string) Container

func (Container) WithPriviledged

func (container Container) WithPriviledged(privileged bool) Container

func (Container) WithStartupTimeout

func (container Container) WithStartupTimeout(startupTimeout time.Duration) Container

func (Container) WithUser

func (container Container) WithUser(user string) Container

type MetricsCollectionTestOption

type MetricsCollectionTestOption func(*metricCollectionTestOpts)

func WithCollectorEnvVars

func WithCollectorEnvVars(envVars map[string]string) MetricsCollectionTestOption

func WithFileMounts

func WithFileMounts(mounts map[string]string) MetricsCollectionTestOption

type OTLPReceiverSink

type OTLPReceiverSink struct {
	Host component.Host

	Logger   *zap.Logger
	Endpoint string
	// contains filtered or unexported fields
}

To be used as a builder whose Build() method provides the actual instance capable of starting the OTLP receiver providing received metrics to test cases.

func NewOTLPReceiverSink

func NewOTLPReceiverSink() OTLPReceiverSink

func (*OTLPReceiverSink) AllLogs

func (otlp *OTLPReceiverSink) AllLogs() []plog.Logs

func (*OTLPReceiverSink) AllMetrics

func (otlp *OTLPReceiverSink) AllMetrics() []pmetric.Metrics

func (*OTLPReceiverSink) AllTraces

func (otlp *OTLPReceiverSink) AllTraces() []ptrace.Traces

func (*OTLPReceiverSink) AssertAllMetricsReceived

func (otlp *OTLPReceiverSink) AssertAllMetricsReceived(t testing.TB, expectedResourceMetrics telemetry.ResourceMetrics, waitTime time.Duration) error

func (OTLPReceiverSink) Build

func (otlp OTLPReceiverSink) Build() (*OTLPReceiverSink, error)

Build will create, configure, and start an OTLPReceiver with GRPC listener and associated metric and log sinks

func (*OTLPReceiverSink) DataPointCount

func (otlp *OTLPReceiverSink) DataPointCount() int

func (*OTLPReceiverSink) LogRecordCount

func (otlp *OTLPReceiverSink) LogRecordCount() int

func (*OTLPReceiverSink) Reset

func (otlp *OTLPReceiverSink) Reset()

func (*OTLPReceiverSink) Shutdown

func (otlp *OTLPReceiverSink) Shutdown() error

func (*OTLPReceiverSink) SpanCount

func (otlp *OTLPReceiverSink) SpanCount() int

func (*OTLPReceiverSink) Start

func (otlp *OTLPReceiverSink) Start() error

func (OTLPReceiverSink) WithEndpoint

func (otlp OTLPReceiverSink) WithEndpoint(endpoint string) OTLPReceiverSink

WithEndpoint is required or Build() will fail

type Testcase

type Testcase struct {
	testing.TB
	Logger                              *zap.Logger
	ObservedLogs                        *observer.ObservedLogs
	OTLPReceiverSink                    *OTLPReceiverSink
	OTLPEndpoint                        string
	OTLPEndpointForCollector            string
	ID                                  string
	OTLPReceiverShouldBindAllInterfaces bool
}

A Testcase is a central helper utility to provide Container, OTLPReceiverSink, ResourceMetrics, SplunkOtelCollector, and ObservedLogs to integration tests with minimal boilerplate. It also embeds testing.TB for easy testing and testify usage.

func NewTestcase

func NewTestcase(t testing.TB) *Testcase

NewTestcase is the recommended constructor that will automatically configure an OTLPReceiverSink with available endpoint and ObservedLogs.

func (*Testcase) Containers

func (t *Testcase) Containers(builders ...Container) (containers []*Container, stop func())

Builds and starts all provided Container builder instances, returning them and a validating stop function.

func (*Testcase) PrintLogsOnFailure

func (t *Testcase) PrintLogsOnFailure()

PrintLogsOnFailure will print all ObserverLogs messages if the test has failed. It's intended to be deferred after Testcase creation. There is a bug in testcontainers-go so it's not certain these are complete: https://github.com/testcontainers/testcontainers-go/pull/323

func (*Testcase) ResourceMetrics

func (t *Testcase) ResourceMetrics(filename string) *telemetry.ResourceMetrics

Loads and validates a ResourceMetrics instance, assuming it's located in ./testdata/resource_metrics

func (*Testcase) ShutdownOTLPReceiverSink

func (t *Testcase) ShutdownOTLPReceiverSink()

Validating shutdown helper for the Testcase's OTLPReceiverSink

func (*Testcase) SplunkOtelCollector

func (t *Testcase) SplunkOtelCollector(configFilename string, builders ...CollectorBuilder) (collector Collector, shutdown func())

SplunkOtelCollector builds and starts a collector container or process using the desired config filename (assuming it's in the ./testdata directory) returning it and a validating shutdown function.

func (*Testcase) SplunkOtelCollectorContainer

func (t *Testcase) SplunkOtelCollectorContainer(configFilename string, builders ...CollectorBuilder) (collector *CollectorContainer, shutdown func())

SplunkOtelCollectorContainer is the same as SplunkOtelCollector but returns *CollectorContainer. If SPLUNK_OTEL_COLLECTOR_IMAGE isn't set, tests that call this will be skipped.

func (*Testcase) SplunkOtelCollectorProcess

func (t *Testcase) SplunkOtelCollectorProcess(configFilename string, builders ...CollectorBuilder) (collector *CollectorProcess, shutdown func())

SplunkOtelCollectorProcess is the same as SplunkOtelCollector but returns *CollectorProcess.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL