/
home
/
sjslayjy
/
public_html
/
theweavenest
/
vendor
/
ramsey
/
uuid
/
tests
/
Provider
/
Node
/
Upload File
HOME
<?php declare(strict_types=1); namespace Ramsey\Uuid\Test\Provider\Node; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Exception\NodeException; use Ramsey\Uuid\Provider\Node\SystemNodeProvider; use Ramsey\Uuid\Test\TestCase; use phpmock\spy\Spy; use function array_shift; use function array_walk; use function gettype; use function is_array; use function strlen; use function vsprintf; use const GLOB_NOSORT; /** * Tests for the SystemNodeProvider class * * The class under test make use of various native functions who's output is * dictated by which environment PHP runs on. Instead of having to run these * tests on each of these environments, the related functions are mocked. The * following functions are concerned: * * - glob * - constant * - passthru * - file_get_contents * - ini_get * * On Linux systems `glob` would normally provide one or more paths were mac * address can be retrieved (using `file_get_contents`). On non-linux systems, * or when the `glob` fails, `passthru` is used to read the mac address from the * command for the relevant environment as provided by `constant('PHP_OS')`. * * Please note that, in order to have robust tests, (the output of) these * functions should ALWAYS be mocked and the amount of times each function * should be run should ALWAYS be specified. * * This will make the tests more verbose but also more bullet-proof. */ class SystemNodeProviderTest extends TestCase { private const MOCK_GLOB = 'glob'; private const MOCK_CONSTANT = 'constant'; private const MOCK_PASSTHRU = 'passthru'; private const MOCK_FILE_GET_CONTENTS = 'file_get_contents'; private const MOCK_INI_GET = 'ini_get'; private const MOCK_IS_READABLE = 'is_readable'; private const PROVIDER_NAMESPACE = 'Ramsey\\Uuid\\Provider\\Node'; /** * @var Spy[] */ private $functionProxies = []; /** * @runInSeparateProcess * @preserveGlobalState disabled * @dataProvider provideValidNetStatOutput */ public function testGetNodeReturnsSystemNodeFromMacAddress(string $netstatOutput, string $expected): void { /* Arrange mocks for native functions */ $this->arrangeMockFunctions( null, null, function () use ($netstatOutput): void { echo $netstatOutput; }, 'NOT LINUX', 'nothing disabled' ); /* Act upon the system under test */ $provider = new SystemNodeProvider(); $node = $provider->getNode(); /* Assert the result match expectations */ $this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']); $this->assertSame($expected, $node->toString()); $message = vsprintf( 'Node should be a hexadecimal string of 12 characters. Actual node: %s (length: %s)', [$node->toString(), strlen($node->toString()),] ); $this->assertMatchesRegularExpression('/^[A-Fa-f0-9]{12}$/', $node->toString(), $message); } /** * @runInSeparateProcess * @preserveGlobalState disabled * @dataProvider provideInvalidNetStatOutput */ public function testGetNodeShouldNotReturnsSystemNodeForInvalidMacAddress(string $netstatOutput): void { /* Arrange */ $this->arrangeMockFunctions( null, null, function () use ($netstatOutput): void { echo $netstatOutput; }, 'NOT LINUX', 'nothing disabled' ); /* Act */ $exception = null; $provider = new SystemNodeProvider(); try { $provider->getNode(); } catch (NodeException $exception) { // do nothing } /* Assert */ $this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']); $this->assertInstanceOf(NodeException::class, $exception); } /** * @runInSeparateProcess * @preserveGlobalState disabled * @dataProvider provideNotationalFormats */ public function testGetNodeReturnsNodeStrippedOfNotationalFormatting(string $formatted, string $expected): void { /* Arrange */ $this->arrangeMockFunctions( null, null, function () use ($formatted): void { echo "\n{$formatted}\n"; }, 'NOT LINUX', 'nothing disabled' ); /* Act */ $provider = new SystemNodeProvider(); $node = $provider->getNode(); /* Assert */ $this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']); $this->assertSame($expected, $node->toString()); } /** * @runInSeparateProcess * @preserveGlobalState disabled * @dataProvider provideInvalidNotationalFormats */ public function testGetNodeDoesNotAcceptIncorrectNotationalFormatting(string $formatted): void { /* Arrange */ $this->arrangeMockFunctions( null, null, function () use ($formatted): void { echo "\n{$formatted}\n"; }, 'NOT LINUX', 'nothing disabled' ); /* Act */ $exception = null; $provider = new SystemNodeProvider(); try { $provider->getNode(); } catch (NodeException $exception) { // do nothing } /* Assert */ $this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']); $this->assertInstanceOf(NodeException::class, $exception); } /** * @runInSeparateProcess * @preserveGlobalState disabled */ public function testGetNodeReturnsFirstMacAddressFound(): void { /* Arrange */ $this->arrangeMockFunctions( null, null, function (): void { echo "\nAA-BB-CC-DD-EE-FF\n00-11-22-33-44-55\nFF-11-EE-22-DD-33\n"; }, 'NOT LINUX', 'nothing disabled' ); /* Act */ $provider = new SystemNodeProvider(); $node = $provider->getNode(); /* Assert */ $this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']); $this->assertSame('aabbccddeeff', $node->toString()); } /** * @runInSeparateProcess * @preserveGlobalState disabled */ public function testGetNodeReturnsFalseWhenNodeIsNotFound(): void { /* Arrange */ $this->arrangeMockFunctions( null, null, function (): void { echo 'some string that does not match the mac address'; }, 'NOT LINUX', 'nothing disabled' ); /* Act */ $exception = null; $provider = new SystemNodeProvider(); try { $provider->getNode(); } catch (NodeException $exception) { // do nothing } /* Assert */ $this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']); $this->assertInstanceOf(NodeException::class, $exception); } /** * @runInSeparateProcess * @preserveGlobalState disabled */ public function testGetNodeWillNotExecuteSystemCallIfFailedFirstTime(): void { /* Arrange */ $this->arrangeMockFunctions( null, null, function (): void { echo 'some string that does not match the mac address'; }, 'NOT LINUX', 'nothing disabled' ); /* Act */ $exception1 = null; $exception2 = null; $provider = new SystemNodeProvider(); try { $provider->getNode(); } catch (NodeException $exception1) { // do nothing } try { $provider->getNode(); } catch (NodeException $exception2) { // do nothing } /* Assert */ $this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']); $this->assertInstanceOf(NodeException::class, $exception1); $this->assertInstanceOf(NodeException::class, $exception2); } /** * @runInSeparateProcess * @preserveGlobalState disabled * @dataProvider provideCommandPerOs */ public function testGetNodeGetsNetworkInterfaceConfig(string $os, string $command): void { /* Arrange */ $this->arrangeMockFunctions( 'whatever', ['mock address path'], 'whatever', $os, 'nothing disabled', true ); /* Act */ $exception = null; $provider = new SystemNodeProvider(); try { $provider->getNode(); } catch (NodeException $exception) { // do nothing } /* Assert */ $globBodyAssert = null; $fileGetContentsAssert = null; $isReadableAssert = null; if ($os === 'Linux') { $globBodyAssert = [['/sys/class/net/*/address', GLOB_NOSORT]]; $fileGetContentsAssert = ['mock address path']; $isReadableAssert = $fileGetContentsAssert; } $this->assertMockFunctions( $fileGetContentsAssert, $globBodyAssert, [$command], ['PHP_OS'], ['disable_functions'], $isReadableAssert ); $this->assertInstanceOf(NodeException::class, $exception); } /** * @runInSeparateProcess * @preserveGlobalState disabled */ public function testGetNodeReturnsSameNodeUponSubsequentCalls(): void { /* Arrange */ $this->arrangeMockFunctions( null, null, function (): void { echo "\nAA-BB-CC-DD-EE-FF\n"; }, 'NOT LINUX', 'nothing disabled' ); /* Act */ $provider = new SystemNodeProvider(); $node = $provider->getNode(); $node2 = $provider->getNode(); /* Assert */ $this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']); $this->assertSame($node->toString(), $node2->toString()); } /** * @runInSeparateProcess * @preserveGlobalState disabled */ public function testSubsequentCallsToGetNodeDoNotRecallIfconfig(): void { /* Arrange */ $this->arrangeMockFunctions( null, null, function (): void { echo "\nAA-BB-CC-DD-EE-FF\n"; }, 'NOT LINUX', 'nothing disabled' ); /* Act */ $provider = new SystemNodeProvider(); $node = $provider->getNode(); $node2 = $provider->getNode(); /* Assert */ $this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']); $this->assertSame($node->toString(), $node2->toString()); } /** * @runInSeparateProcess * @preserveGlobalState disabled * @dataProvider provideCommandPerOs */ public function testCallGetsysfsOnLinux(string $os, string $command): void { /* Arrange */ $this->arrangeMockFunctions( function () { static $macs = ["00:00:00:00:00:00\n", "01:02:03:04:05:06\n"]; return array_shift($macs); }, ['mock address path 1', 'mock address path 2'], function (): void { echo "\n01-02-03-04-05-06\n"; }, $os, 'nothing disabled', true ); /* Act */ $provider = new SystemNodeProvider(); $node = $provider->getNode(); /* Assert */ $fileGetContentsAssert = null; $globBodyAssert = null; $passthruBodyAssert = [$command]; $constantBodyAssert = ['PHP_OS']; $iniGetDisableFunctionsAssert = ['disable_functions']; $isReadableAssert = null; if ($os === 'Linux') { $fileGetContentsAssert = [['mock address path 1'], ['mock address path 2']]; $globBodyAssert = [['/sys/class/net/*/address', GLOB_NOSORT]]; $passthruBodyAssert = null; $constantBodyAssert = ['PHP_OS']; $iniGetDisableFunctionsAssert = null; $isReadableAssert = $fileGetContentsAssert; } $this->assertMockFunctions( $fileGetContentsAssert, $globBodyAssert, $passthruBodyAssert, $constantBodyAssert, $iniGetDisableFunctionsAssert, $isReadableAssert ); $this->assertSame('010203040506', $node->toString()); } /** * @runInSeparateProcess * @preserveGlobalState disabled */ public function testCallGetsysfsOnLinuxWhenGlobReturnsFalse(): void { /* Arrange */ $this->arrangeMockFunctions( null, false, function (): void { echo "\n01-02-03-04-05-06\n"; }, 'Linux', 'nothing disabled' ); /* Act */ $provider = new SystemNodeProvider(); $node = $provider->getNode(); /* Assert */ $this->assertMockFunctions( null, [['/sys/class/net/*/address', GLOB_NOSORT]], ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions'] ); $this->assertSame('010203040506', $node->toString()); } /** * @runInSeparateProcess * @preserveGlobalState disabled */ public function testCallGetsysfsOnLinuxWhenGlobReturnsEmptyArray(): void { /* Arrange */ $this->arrangeMockFunctions( null, [], function (): void { echo "\n01-02-03-04-05-06\n"; }, 'Linux', 'nothing disabled' ); /* Act */ $provider = new SystemNodeProvider(); $node = $provider->getNode(); /* Assert */ $this->assertMockFunctions( null, [['/sys/class/net/*/address', GLOB_NOSORT]], ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions'] ); $this->assertSame('010203040506', $node->toString()); } /** * @runInSeparateProcess * @preserveGlobalState disabled */ public function testCallGetsysfsOnLinuxWhenGlobFilesAreNotReadable(): void { /* Arrange */ $this->arrangeMockFunctions( null, ['mock address path 1', 'mock address path 2'], function (): void { echo "\n01-02-03-04-05-06\n"; }, 'Linux', 'nothing disabled', false ); /* Act */ $provider = new SystemNodeProvider(); $node = $provider->getNode(); /* Assert */ $this->assertMockFunctions( null, [['/sys/class/net/*/address', GLOB_NOSORT]], ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions'], ['mock address path 1', 'mock address path 2'] ); $this->assertSame('010203040506', $node->toString()); } /** * @runInSeparateProcess * @preserveGlobalState disabled */ public function testGetNodeReturnsFalseWhenPassthruIsDisabled(): void { /* Arrange */ $this->arrangeMockFunctions( null, null, null, 'NOT LINUX', 'PASSTHRU,some_other_function' ); /* Act */ $exception = null; $provider = new SystemNodeProvider(); try { $provider->getNode(); } catch (NodeException $exception) { // do nothing } /* Assert */ $this->assertMockFunctions( null, null, null, ['PHP_OS'], ['disable_functions'] ); $this->assertInstanceOf(NodeException::class, $exception); } /** * Replaces the return value for functions with the given value or callback. * * @param callback|mixed|null $fileGetContentsBody * @param callback|mixed|null $globBody * @param callback|mixed|null $passthruBody * @param callback|mixed|null $constantBody * @param callback|mixed|null $iniGetDisableFunctionsBody * @param callback|mixed|null $isReadableBody */ private function arrangeMockFunctions( $fileGetContentsBody, $globBody, $passthruBody, $constantBody, $iniGetDisableFunctionsBody, $isReadableBody = true ): void { $mockFunction = [ self::MOCK_FILE_GET_CONTENTS => $fileGetContentsBody, self::MOCK_GLOB => $globBody, self::MOCK_PASSTHRU => $passthruBody, self::MOCK_CONSTANT => $constantBody, self::MOCK_INI_GET => $iniGetDisableFunctionsBody, self::MOCK_IS_READABLE => $isReadableBody, ]; array_walk($mockFunction, function ($body, $key): void { if (!is_callable($body)) { $body = function () use ($body) { return $body; }; } $spy = new Spy(self::PROVIDER_NAMESPACE, $key, $body); $spy->enable(); $this->functionProxies[$key] = $spy; }); } /** * Verifies that each function was called exactly once for each assert given. * * Provide a NULL to assert a function is never called. * * @param array<int, string>|array<int, array<int,string>>|null $fileGetContentsAssert * @param array<int, array<int, int|string>>|null $globBodyAssert * @param array<int, string>|array<int, array<int,string>>|null $passthruBodyAssert * @param array<int, string>|array<int, array<int,string>>|null $constantBodyAssert * @param array<int, string>|array<int, array<int,string>>|null $iniGetDisableFunctionsAssert * @param array<int, string>|array<int, array<int,string>>|null $isReadableAssert */ private function assertMockFunctions( ?array $fileGetContentsAssert, ?array $globBodyAssert, ?array $passthruBodyAssert, ?array $constantBodyAssert, ?array $iniGetDisableFunctionsAssert, ?array $isReadableAssert = null ): void { $mockFunctionAsserts = [ self::MOCK_FILE_GET_CONTENTS => $fileGetContentsAssert, self::MOCK_GLOB => $globBodyAssert, self::MOCK_PASSTHRU => $passthruBodyAssert, self::MOCK_CONSTANT => $constantBodyAssert, self::MOCK_INI_GET => $iniGetDisableFunctionsAssert, self::MOCK_IS_READABLE => $isReadableAssert, ]; array_walk($mockFunctionAsserts, function ($asserts, $key): void { if ($asserts === null) { // Assert the function was never invoked. $this->assertEmpty($this->functionProxies[$key]->getInvocations()); } elseif (is_array($asserts)) { // Assert there was at least one invocation for this function. $this->assertNotEmpty($this->functionProxies[$key]->getInvocations()); $invokedArgs = []; foreach ($this->functionProxies[$key]->getInvocations() as $invocation) { $invokedArgs[] = $invocation->getArguments(); } foreach ($asserts as $assert) { // Assert these args were used to invoke the function. $assert = is_array($assert) ? $assert : [$assert]; $this->assertContains($assert, $invokedArgs); } } else { $error = vsprintf( 'Given parameter for %s must be an array or NULL, "%s" given.', [$key, gettype($asserts)] ); throw new InvalidArgumentException($error); } }); } /** * Provides the command that should be executed per supported OS * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification */ public function provideCommandPerOs(): array { return [ 'windows' => ['Windows', 'ipconfig /all 2>&1'], 'mac' => ['Darwhat', 'ifconfig 2>&1'], 'linux' => ['Linux', 'netstat -ie 2>&1'], 'freebsd' => ['FreeBSD', 'netstat -i -f link 2>&1'], 'anything_else' => ['someotherxyz', 'netstat -ie 2>&1'], 'Linux when `glob` fails' => ['LIN', 'netstat -ie 2>&1'], ]; } /** * Values that are NOT parsed to a mac address by the class under test * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification */ public function provideInvalidNetStatOutput(): array { return [ 'Not an octal value' => [ "The program 'netstat' is currently not installed. " . "You can install it by typing:\nsudo apt install net-tools\n", ], 'One character too short' => ["\nA-BB-CC-DD-EE-FF\n"], 'One tuple too short' => ["\nBB-CC-DD-EE-FF\n"], 'With colon, with linebreak, without space' => ["\n:AA-BB-CC-DD-EE-FF\n"], 'With colon, without linebreak, with space' => [' : AA-BB-CC-DD-EE-FF'], 'With colon, without linebreak, without space' => [':AA-BB-CC-DD-EE-FF'], 'Without colon, without linebreak, without space' => ['AA-BB-CC-DD-EE-FF'], 'Without leading linebreak' => ["AA-BB-CC-DD-EE-FF\n"], 'Without leading whitespace' => ['AA-BB-CC-DD-EE-FF '], 'Without trailing linebreak' => ["\nAA-BB-CC-DD-EE-FF"], 'Without trailing whitespace' => [' AA-BB-CC-DD-EE-FF'], 'All zero MAC address' => ['00-00-00-00-00-00'], ]; } /** * Provides notations that the class under test should NOT attempt to strip * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification */ public function provideInvalidNotationalFormats(): array { return [ ['01:23-45-67-89-ab'], ['01:23:45-67-89-ab'], ['01:23:45:67-89-ab'], ['01:23:45:67:89-ab'], ['01-23:45:67:89:ab'], ['01-23-45:67:89:ab'], ['01-23-45-67:89:ab'], ['01-23-45-67-89:ab'], ['00:00:00:00:00:00'], ]; } /** * Provides mac addresses that the class under test should strip notational format from * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification */ public function provideNotationalFormats(): array { return [ ['01-23-45-67-89-ab', '0123456789ab'], ['01:23:45:67:89:ab', '0123456789ab'], ]; } /** * Values that are parsed to a mac address by the class under test * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification */ public function provideValidNetStatOutput(): array { return [ /* Full output of related command */ 'Full output - Linux' => [<<<'TXT' Kernel Interface table docker0 Link encap:Ethernet HWaddr 01:23:45:67:89:ab inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0 UP BROADCAST MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) enp3s0 Link encap:Ethernet HWaddr fe:dc:ba:98:76:54 inet addr:10.0.0.1 Bcast:10.0.0.255 Mask:255.255.255.0 inet6 addr: ffee::ddcc:bbaa:9988:7766/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:943077 errors:0 dropped:0 overruns:0 frame:0 TX packets:2168039 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:748596414 (748.5 MB) TX bytes:2930448282 (2.9 GB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:8302 errors:0 dropped:0 overruns:0 frame:0 TX packets:8302 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:1094983 (1.0 MB) TX bytes:1094983 (1.0 MB) TXT , '0123456789ab', ], 'Full output - MacOS' => [<<<'TXT' lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384 options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP> inet 127.0.0.1 netmask 0xff000000 inet6 ::1 prefixlen 128 inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 nd6 options=201<PERFORMNUD,DAD> gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280 stf0: flags=0<> mtu 1280 EHC29: flags=0<> mtu 0 XHC20: flags=0<> mtu 0 EHC26: flags=0<> mtu 0 aa0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 options=10b<RXCSUM,TXCSUM,VLAN_HWTAGGING,AV> ether 00:00:00:00:00:00 status: active en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 options=10b<RXCSUM,TXCSUM,VLAN_HWTAGGING,AV> ether 10:dd:b1:b4:e4:8e inet6 fe80::c70:76f5:aa1:5db1%en0 prefixlen 64 secured scopeid 0x7 inet 10.53.8.112 netmask 0xfffffc00 broadcast 10.53.11.255 nd6 options=201<PERFORMNUD,DAD> media: autoselect (1000baseT <full-duplex>) status: active en1: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 ether ec:35:86:38:c8:c2 inet6 fe80::aa:d44f:5f5f:7fd4%en1 prefixlen 64 secured scopeid 0x8 inet 10.53.17.196 netmask 0xfffffc00 broadcast 10.53.19.255 nd6 options=201<PERFORMNUD,DAD> media: autoselect status: active p2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304 ether 0e:35:86:38:c8:c2 media: autoselect status: inactive awdl0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1484 ether ea:ab:ae:25:f5:d0 inet6 fe80::e8ab:aeff:fe25:f5d0%awdl0 prefixlen 64 scopeid 0xa nd6 options=201<PERFORMNUD,DAD> media: autoselect status: active en2: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500 options=60<TSO4,TSO6> ether 32:00:18:9b:dc:60 media: autoselect <full-duplex> status: inactive en3: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500 options=60<TSO4,TSO6> ether 32:00:18:9b:dc:61 media: autoselect <full-duplex> status: inactive bridge0: flags=8822<BROADCAST,SMART,SIMPLEX,MULTICAST> mtu 1500 options=63<RXCSUM,TXCSUM,TSO4,TSO6> ether 32:00:18:9b:dc:60 Configuration: id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0 maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200 root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0 ipfilter disabled flags 0x2 member: en2 flags=3<LEARNING,DISCOVER> ifmaxaddr 0 port 11 priority 0 path cost 0 member: en3 flags=3<LEARNING,DISCOVER> ifmaxaddr 0 port 12 priority 0 path cost 0 media: <unknown type> status: inactive utun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 2000 options=6403<RXCSUM,TXCSUM,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM> inet6 fe80::57c6:d692:9d41:d28f%utun0 prefixlen 64 scopeid 0xe nd6 options=201<PERFORMNUD,DAD> TXT , '10ddb1b4e48e', ], 'Full output - Window' => [<<<'TXT' Windows IP Configuration Host Name . . . . . . . . . . . . : MSEDGEWIN10 Primary Dns Suffix . . . . . . . : Node Type . . . . . . . . . . . . : Hybrid IP Routing Enabled. . . . . . . . : No WINS Proxy Enabled. . . . . . . . : No DNS Suffix Search List. . . . . . : network.lan Some kind of adapter: Connection-specific DNS Suffix . : network.foo Description . . . . . . . . . . . : Some Adapter Physical Address. . . . . . . . . : 00-00-00-00-00-00 Ethernet adapter Ethernet: Connection-specific DNS Suffix . : network.lan Description . . . . . . . . . . . : Intel(R) PRO/1000 MT Desktop Adapter Physical Address. . . . . . . . . : 08-00-27-B8-42-C6 DHCP Enabled. . . . . . . . . . . : Yes Autoconfiguration Enabled . . . . : Yes Link-local IPv6 Address . . . . . : fe80::606a:ae33:7ce1:b5e9%3(Preferred) IPv4 Address. . . . . . . . . . . : 10.0.2.15(Preferred) Subnet Mask . . . . . . . . . . . : 255.255.255.0 Lease Obtained. . . . . . . . . . : Tuesday, January 30, 2018 11:25:31 PM Lease Expires . . . . . . . . . . : Wednesday, January 31, 2018 11:25:27 PM Default Gateway . . . . . . . . . : 10.0.2.2 DHCP Server . . . . . . . . . . . : 10.0.2.2 DHCPv6 IAID . . . . . . . . . . . : 34078759 DHCPv6 Client DUID. . . . . . . . : 00-01-00-01-21-40-72-3F-08-00-27-B8-42-C6 DNS Servers . . . . . . . . . . . : 10.0.2.3 NetBIOS over Tcpip. . . . . . . . : Enabled Tunnel adapter isatap.network.lan: Media State . . . . . . . . . . . : Media disconnected Connection-specific DNS Suffix . : network.lan Description . . . . . . . . . . . : Microsoft ISATAP Adapter Physical Address. . . . . . . . . : 00-00-00-00-00-00-00-E0 DHCP Enabled. . . . . . . . . . . : No Autoconfiguration Enabled . . . . : Yes TXT , '080027b842c6', ], 'Full output - FreeBSD' => [<<<'TXT' Name Mtu Network Address Ipkts Ierrs Idrop Opkts Oerrs Coll aa0 0 <Link#0> 00:00:00:00:00:00 0 0 0 0 0 0 em0 1500 <Link#1> 08:00:27:71:a1:00 65514 0 0 42918 0 0 em1 1500 <Link#2> 08:00:27:d0:60:a0 1199 0 0 535 0 0 lo0 16384 <Link#3> lo0 4 0 0 4 0 0 TXT , '08002771a100', ], /* The single line that is relevant */ 'Linux - single line' => ["\ndocker0 Link encap:Ethernet HWaddr 01:23:45:67:89:ab\n", '0123456789ab'], 'MacOS - Single line ' => ["\nether 10:dd:b1:b4:e4:8e\n", '10ddb1b4e48e'], 'Window - single line' => ["\nPhysical Address. . . . . . . . . : 08-00-27-B8-42-C6\n", '080027b842c6'], /* Minimal subsets of the single line to show the differences */ 'with colon, with linebreak, with space' => ["\n : AA-BB-CC-DD-EE-FF\n", 'aabbccddeeff'], 'without colon, with linebreak, with space' => ["\n AA-BB-CC-DD-EE-FF \n", 'aabbccddeeff'], 'without colon, with linebreak, without space' => ["\nAA-BB-CC-DD-EE-FF\n", 'aabbccddeeff'], 'without colon, without linebreak, with space' => [' AA-BB-CC-DD-EE-FF ', 'aabbccddeeff'], /* Other accepted variations */ 'Actual mac - 1' => ["\n52:54:00:14:91:69\n", '525400149169'], 'Actual mac - 2' => ["\n00:16:3e:a9:73:f0\n", '00163ea973f0'], 'FF:FF:FF:FF:FF:FF' => ["\nFF:FF:FF:FF:FF:FF\n", 'ffffffffffff'], /* Incorrect variations that are also accepted */ 'Too long -- extra character' => ["\nABC-01-23-45-67-89\n", 'bc0123456789'], 'Too long -- extra tuple' => ["\n01-AA-BB-CC-DD-EE-FF\n", '01aabbccddee'], ]; } }