# Short-name access to TestContext devices

## Description

When a device server is launched via the `DeviceTestContext` or `MultiDeviceTestContext`, it is run without a Tango database. This means that access to any of its devices must be done via a fully-qualified Tango Resource Locator (FQTRL). I.e., we need something like `tango://172.17.0.3:48493/test/device/1#dbase=no`, instead of just the short name: `test/device/1`. Requiring the FQTRL is inconvenient.

A simple example:

    from tango import AttrWriteType, DeviceProxy
    from tango.server import Device, attribute
    from tango.test_context import MultiDeviceTestContext

    class Device1(Device):
        _attr1 = 100

        @attribute(access=AttrWriteType.READ_WRITE)
        def attr1(self):
            return self._attr1

        def write_attr1(self, value):
            self._attr1 = value

    def test_multi_short_name_device_proxy_access_without_tango_db():
        devices_info = ({"class": Device1, "devices": [{"name": "test/device1/1"}]},)

        with MultiDeviceTestContext(devices_info):
            proxy1 = tango.DeviceProxy("test/device1/1")
            assert proxy1.name() == "test/device1/1"
            assert proxy1.attr1 == 100

Previously, we would have had to use a call like `context.get_device_access("test/device1/1")` to get the FQTRL, before instantiating a `DeviceProxy`.

A more detailed example is available.

## Limitations

- Group patterns (`*` wildcard) are not supported.

- Group device names will be FQTRLs, rather than the short name that was originally supplied.

- Launching two TestContexts in the same process will not work correctly without FQTRLs. The TestContext that is started second will overwrite the global variable set by the first.

## Implementation details

From PyTango 9.5.0, the test device server’s FQTRL is stored in in a global variable while the TextContext is active. Then, in order to make device access with short names transparent, the `DeviceProxy`, `AttributeProxy` and `Group` classes have been modified to take this global variable into account. If it is set, and a short (i.e., unresolved) TRL is used for the proxy constructor or group add method, then the name is rewritten to a fully qualified (resolved) TRL. If this FQTRL is not accessible, the short name is tried again (except for Group) - this allows real devices to be accessed via short TRLs, if necessary.

There should not be a noticeable performance degradation for this outside of test environments. It it just a single variable lookup per proxy constructor or group add invocation.

This new TestContext functionality is enabled by default. It can be disabled by setting the enable_test_context_tango_host_override attribute to False before starting the TestContext.

Example:

    def test_multi_short_name_access_fails_if_override_disabled():
        devices_info = ({"class": Device1, "devices": [{"name": "test/device1/a"}]},)

        context = MultiDeviceTestContext(devices_info)
        context.enable_test_context_tango_host_override = False
        context.start()
        try:
            with pytest.raises(DevFailed):
                _ = tango.DeviceProxy("test/device1/a")
        finally:
            context.stop()

## Port detection and pre_init_callback

As before, if the user doesn’t specify a port number when instantiating the TestContext, omniORB will select an ephemeral port automatically when the device server starts. We need to determine this port number before any device calls its init_device method, in case it want to create clients (`DeviceProxy`, etc.) at that early stage.

The solution to this is the addition of a `pre_init_callback` in the `run()` and `run_server()` methods. This is called after `Util.init()` (when omniORB has started and bound to a TCP port for the GIOP traffic, and the event system has bound two ports for ZeroMQ traffic), but before `Util.server_init()` (which initialises all classes in the device server).

During `pre_init_callback` the TestContext probes the process’s open ports to determine the GIOP port number. We didn’t find a way to determine the port number without probing each port.