mirror of https://github.com/esphome/esphome.git
Merge 3ae611844a
into c7c0d97a5e
This commit is contained in:
commit
8a0ff5d591
|
@ -3,6 +3,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mdns {
|
||||
|
@ -35,6 +36,8 @@ class MDNSComponent : public Component {
|
|||
|
||||
void add_extra_service(MDNSService service) { services_extra_.push_back(std::move(service)); }
|
||||
|
||||
std::vector<network::IPAddress> resolve(const std::string &servicename);
|
||||
|
||||
void on_shutdown() override;
|
||||
|
||||
protected:
|
||||
|
@ -44,5 +47,7 @@ class MDNSComponent : public Component {
|
|||
void compile_records_();
|
||||
};
|
||||
|
||||
extern MDNSComponent *global_mdns; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace mdns {
|
|||
static const char *const TAG = "mdns";
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
global_mdns = this;
|
||||
this->compile_records_();
|
||||
|
||||
esp_err_t err = mdns_init();
|
||||
|
@ -48,11 +49,43 @@ void MDNSComponent::setup() {
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<network::IPAddress> MDNSComponent::resolve(const std::string &servicename) {
|
||||
std::vector<network::IPAddress> resolved;
|
||||
mdns_result_t *results = nullptr;
|
||||
mdns_ip_addr_t *a = nullptr;
|
||||
esp_err_t err = mdns_query_ptr(("_" + servicename).c_str(), "_tcp", 3000, 20, &results);
|
||||
if (err) {
|
||||
ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
|
||||
return {};
|
||||
}
|
||||
if (!results) {
|
||||
ESP_LOGW(TAG, "No results found!");
|
||||
return {};
|
||||
}
|
||||
while (results) {
|
||||
a = results->addr;
|
||||
while (a) {
|
||||
network::IPAddress ip_addr = network::IPAddress(&a->addr);
|
||||
if (std::count(resolved.begin(), resolved.end(), ip_addr) == 0) {
|
||||
resolved.push_back(ip_addr);
|
||||
}
|
||||
ESP_LOGVV(TAG, "Found mDNS %s", ip_addr.str().c_str());
|
||||
a = a->next;
|
||||
}
|
||||
results = results->next;
|
||||
}
|
||||
|
||||
mdns_query_results_free(results);
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
void MDNSComponent::on_shutdown() {
|
||||
mdns_free();
|
||||
delay(40); // Allow the mdns packets announcing service removal to be sent
|
||||
}
|
||||
|
||||
MDNSComponent *global_mdns = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
static const char *const TAG = "mdns";
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
global_mdns = this;
|
||||
this->compile_records_();
|
||||
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
@ -34,6 +37,18 @@ void MDNSComponent::setup() {
|
|||
}
|
||||
}
|
||||
}
|
||||
std::vector<network::IPAddress> MDNSComponent::resolve(const std::string &servicename) {
|
||||
std::vector<network::IPAddress> resolved;
|
||||
uint8_t n = MDNS.queryService(servicename.c_str(), "tcp");
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
network::IPAddress ip_addr = network::IPAddress(MDNS.IP(i));
|
||||
if (std::count(resolved.begin(), resolved.end(), ip_addr) == 0) {
|
||||
resolved.push_back(ip_addr);
|
||||
}
|
||||
ESP_LOGVV(TAG, "Found mDNS %s", ip_addr.str().c_str());
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
void MDNSComponent::loop() { MDNS.update(); }
|
||||
|
||||
|
@ -42,6 +57,8 @@ void MDNSComponent::on_shutdown() {
|
|||
delay(10);
|
||||
}
|
||||
|
||||
MDNSComponent *global_mdns = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace esphome {
|
|||
namespace mdns {
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
global_mdns = this;
|
||||
this->compile_records_();
|
||||
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
@ -35,8 +36,12 @@ void MDNSComponent::setup() {
|
|||
}
|
||||
}
|
||||
|
||||
// Libre tiny doesn't have a "full" mDNS implementation
|
||||
std::vector<network::IPAddress> MDNSComponent::resolve(const std::string &servicename) { return {}; }
|
||||
|
||||
void MDNSComponent::on_shutdown() {}
|
||||
|
||||
MDNSComponent *global_mdns = nullptr;
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
static const char *const TAG = "mdns";
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
global_mdns = this;
|
||||
this->compile_records_();
|
||||
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
@ -35,6 +38,19 @@ void MDNSComponent::setup() {
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<network::IPAddress> MDNSComponent::resolve(const std::string &servicename) {
|
||||
std::vector<network::IPAddress> resolved;
|
||||
uint8_t n = MDNS.queryService(servicename.c_str(), "tcp");
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
network::IPAddress ip_addr_ = network::IPAddress(MDNS.IP(i));
|
||||
if (std::count(resolved.begin(), resolved.end(), ip_addr_) == 0) {
|
||||
resolved.push_back(ip_addr_);
|
||||
}
|
||||
ESP_LOGVV(TAG, "Found mDNS %s", ip_addr_.str().c_str());
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
void MDNSComponent::loop() { MDNS.update(); }
|
||||
|
||||
void MDNSComponent::on_shutdown() {
|
||||
|
@ -42,6 +58,7 @@ void MDNSComponent::on_shutdown() {
|
|||
delay(40);
|
||||
}
|
||||
|
||||
MDNSComponent *global_mdns = nullptr;
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
from esphome.core import CORE
|
||||
from __future__ import annotations
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import helpers
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ENABLE_IPV6,
|
||||
CONF_MIN_IPV6_ADDR_COUNT,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
CONF_HOSTS,
|
||||
CONF_HOSTSFILE,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_NAME,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["mdns"]
|
||||
|
||||
network_ns = cg.esphome_ns.namespace("network")
|
||||
IPAddress = network_ns.class_("IPAddress")
|
||||
Resolver = network_ns.class_("Resolver")
|
||||
CONF_NETWORK_ID = "network_id"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
|
@ -23,10 +31,34 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
cv.boolean, cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040])
|
||||
),
|
||||
cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
|
||||
cv.GenerateID(CONF_NETWORK_ID): cv.declare_id(Resolver),
|
||||
cv.Optional(CONF_HOSTSFILE): cv.file_,
|
||||
cv.Optional(CONF_HOSTS): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
cv.Required(CONF_IP_ADDRESS): cv.string,
|
||||
}
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def parse_hosts_file(hosts_contents: str) -> list[tuple[str, IPAddress]]:
|
||||
"""Parse a hosts file"""
|
||||
hosts: list[tuple[str, IPAddress]] = []
|
||||
for line in hosts_contents.splitlines():
|
||||
if line.startswith("#"):
|
||||
continue
|
||||
split_line = line.split()
|
||||
if len(split_line) < 2:
|
||||
continue
|
||||
ip_address: IPAddress = IPAddress(split_line[0])
|
||||
hosts.extend([(host, ip_address) for host in split_line[1:]])
|
||||
return hosts
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
if CONF_ENABLE_IPV6 in config:
|
||||
cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6])
|
||||
|
@ -46,3 +78,17 @@ async def to_code(config):
|
|||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
|
||||
if CORE.is_esp8266:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
|
||||
hosts = []
|
||||
if CONF_HOSTS in config:
|
||||
hosts = [
|
||||
(host[CONF_NAME], IPAddress(host[CONF_IP_ADDRESS]))
|
||||
for host in config[CONF_HOSTS]
|
||||
]
|
||||
if CONF_HOSTSFILE in config:
|
||||
hosts_contents = helpers.read_file(
|
||||
CORE.relative_config_path(config[CONF_HOSTSFILE])
|
||||
)
|
||||
hosts.extend(parse_hosts_file(hosts_contents))
|
||||
|
||||
map_ = cg.std_ns.class_("multimap").template(cg.std_string, IPAddress)
|
||||
cg.new_Pvariable(config[CONF_NETWORK_ID], map_(hosts))
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
#include <vector>
|
||||
#include "resolver.h"
|
||||
#include "lwip/dns.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_MDNS
|
||||
#include "esphome/components/mdns/mdns_component.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_IPV6
|
||||
#define ESPHOME_DNS_ADDRTYPE LWIP_DNS_ADDRTYPE_IPV6_IPV4
|
||||
#else
|
||||
#define ESPHOME_DNS_ADDRTYPE LWIP_DNS_ADDRTYPE_IPV4
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace network {
|
||||
|
||||
static const char *const TAG = "resolver";
|
||||
|
||||
Resolver::Resolver() { global_resolver = this; }
|
||||
Resolver::Resolver(std::multimap<std::string, network::IPAddress> hosts) : hosts_(std::move(hosts)) {
|
||||
global_resolver = this;
|
||||
}
|
||||
// TODO(HeMan): resolve needs to return multiple IP addresses
|
||||
std::vector<network::IPAddress> Resolver::resolve(const std::string &hostname) {
|
||||
if (this->hosts_.count(hostname) > 0) {
|
||||
std::vector<network::IPAddress> resolved;
|
||||
for (auto a = hosts_.find(hostname); a != hosts_.end(); a++) {
|
||||
resolved.push_back(a->second);
|
||||
ESP_LOGVV(TAG, "Found %s in hosts section", hostname.c_str());
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
#ifdef USE_MDNS
|
||||
ESP_LOGV(TAG, "Looking for %s with mDNS", hostname.c_str());
|
||||
std::vector<network::IPAddress> resolved_mdns = mdns::global_mdns->resolve(hostname);
|
||||
if (!resolved_mdns.empty()) {
|
||||
ESP_LOGVV(TAG, "Found %s in mDNS", hostname.c_str());
|
||||
return resolved_mdns;
|
||||
}
|
||||
#endif
|
||||
ip_addr_t addr;
|
||||
ESP_LOGVV(TAG, "Resolving %s", hostname.c_str());
|
||||
err_t err =
|
||||
dns_gethostbyname_addrtype(hostname.c_str(), &addr, Resolver::dns_found_callback, this, ESPHOME_DNS_ADDRTYPE);
|
||||
if (err == ERR_OK) {
|
||||
return {network::IPAddress(&addr)};
|
||||
}
|
||||
this->connect_begin_ = millis();
|
||||
while (!this->dns_resolved_ && !this->dns_resolve_error_ && (millis() - this->connect_begin_ < 2000)) {
|
||||
switch (err) {
|
||||
case ERR_OK: {
|
||||
// Got IP immediately
|
||||
ESP_LOGVV(TAG, "Found %s in DNS", hostname.c_str());
|
||||
this->dns_resolved_ = true;
|
||||
this->ip_ = network::IPAddress(&addr);
|
||||
return {this->ip_};
|
||||
}
|
||||
case ERR_INPROGRESS: {
|
||||
// wait for callback
|
||||
ESP_LOGVV(TAG, "Resolving IP address...");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case ERR_ARG: {
|
||||
// error
|
||||
ESP_LOGW(TAG, "Error resolving IP address: %d", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
delay_microseconds_safe(100);
|
||||
}
|
||||
if (this->dns_resolve_error_) {
|
||||
ESP_LOGV(TAG, "Error resolving IP address");
|
||||
}
|
||||
if (!this->dns_resolved_) {
|
||||
ESP_LOGVV(TAG, "Not resolved");
|
||||
}
|
||||
return {this->ip_};
|
||||
}
|
||||
|
||||
void Resolver::dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) {
|
||||
auto *a_this = (Resolver *) callback_arg;
|
||||
if (ipaddr == nullptr) {
|
||||
a_this->dns_resolve_error_ = true;
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "Found %s in DNS", name);
|
||||
a_this->ip_ = network::IPAddress(ipaddr);
|
||||
a_this->dns_resolved_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
Resolver *global_resolver = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace network
|
||||
} // namespace esphome
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "ip_address.h"
|
||||
namespace esphome {
|
||||
namespace network {
|
||||
|
||||
class Resolver {
|
||||
public:
|
||||
Resolver();
|
||||
Resolver(std::multimap<std::string, network::IPAddress> hosts);
|
||||
~Resolver();
|
||||
std::vector<network::IPAddress> resolve(const std::string &hostname);
|
||||
|
||||
protected:
|
||||
static void dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg);
|
||||
std::multimap<std::string, network::IPAddress> hosts_;
|
||||
network::IPAddress ip_;
|
||||
bool dns_resolved_{false};
|
||||
bool dns_resolve_error_{false};
|
||||
uint32_t connect_begin_;
|
||||
};
|
||||
|
||||
extern Resolver *global_resolver; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace network
|
||||
} // namespace esphome
|
|
@ -1,3 +1,4 @@
|
|||
#include <map>
|
||||
#include "util.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ extern "C" {
|
|||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace wifi {
|
||||
|
|
|
@ -335,6 +335,8 @@ CONF_HIDDEN = "hidden"
|
|||
CONF_HIDE_TIMESTAMP = "hide_timestamp"
|
||||
CONF_HIGH = "high"
|
||||
CONF_HIGH_VOLTAGE_REFERENCE = "high_voltage_reference"
|
||||
CONF_HOSTS = "hosts"
|
||||
CONF_HOSTSFILE = "hostsfile"
|
||||
CONF_HOUR = "hour"
|
||||
CONF_HOURS = "hours"
|
||||
CONF_HUMIDITY = "humidity"
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# IPv4 test
|
||||
192.168.1.1 other.example.com
|
||||
# IPv6 test
|
||||
2001:db8:4455:6677::35 other6.example.com
|
||||
# Empty line test
|
||||
|
||||
# multi name test
|
||||
192.168.1.10 multi1.example.com multi1
|
||||
|
|
@ -80,6 +80,14 @@ wifi:
|
|||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
hosts:
|
||||
- name: www.example.com
|
||||
ip_address: 192.168.1.80
|
||||
- name: www.mqtt.com
|
||||
ip_address: 10.20.18.33
|
||||
- name: ipv6.example.com
|
||||
ip_address: 2001:db8:4455:6677::32
|
||||
hostsfile: hosts
|
||||
|
||||
mdns:
|
||||
disabled: false
|
||||
|
|
|
@ -36,6 +36,14 @@ ethernet:
|
|||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
hosts:
|
||||
- name: www.example.com
|
||||
ip_address: 192.168.1.80
|
||||
- name: www.mqtt.com
|
||||
ip_address: 10.20.18.33
|
||||
- name: ipv6.example.com
|
||||
ip_address: 2001:db8:4455:6677::32
|
||||
hostsfile: hosts
|
||||
|
||||
mdns:
|
||||
disabled: true
|
||||
|
|
|
@ -29,6 +29,14 @@ ethernet:
|
|||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
hosts:
|
||||
- name: www.example.com
|
||||
ip_address: 192.168.1.80
|
||||
- name: www.mqtt.com
|
||||
ip_address: 10.20.18.33
|
||||
- name: ipv6.example.com
|
||||
ip_address: 2001:db8:4455:6677::32
|
||||
hostsfile: hosts
|
||||
|
||||
mqtt:
|
||||
broker: test.mosquitto.org
|
||||
|
|
|
@ -24,6 +24,13 @@ wifi:
|
|||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
hosts:
|
||||
- name: www.example.com
|
||||
ip_address: 192.168.1.80
|
||||
- name: www.mqtt.com
|
||||
ip_address: 10.20.18.33
|
||||
- name: ipv6.example.com
|
||||
ip_address: 2001:db8:4455:6677::32
|
||||
|
||||
api:
|
||||
|
||||
|
|
|
@ -18,6 +18,14 @@ wifi:
|
|||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
hosts:
|
||||
- name: www.example.com
|
||||
ip_address: 192.168.1.80
|
||||
- name: www.mqtt.com
|
||||
ip_address: 10.20.18.33
|
||||
- name: ipv6.example.com
|
||||
ip_address: 2001:db8:4455:6677::32
|
||||
hostsfile: hosts
|
||||
|
||||
api:
|
||||
|
||||
|
|
|
@ -5,6 +5,14 @@ wifi:
|
|||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
hosts:
|
||||
- name: www.example.com
|
||||
ip_address: 192.168.1.80
|
||||
- name: www.mqtt.com
|
||||
ip_address: 10.20.18.33
|
||||
- name: ipv6.example.com
|
||||
ip_address: 2001:db8:4455:6677::32
|
||||
hostsfile: hosts
|
||||
|
||||
esp32:
|
||||
board: lolin_c3_mini
|
||||
|
|
|
@ -9,6 +9,16 @@ rtl87xx:
|
|||
esphome:
|
||||
name: rtl87xx-test
|
||||
|
||||
network:
|
||||
hosts:
|
||||
- name: www.example.com
|
||||
ip_address: 192.168.1.80
|
||||
- name: www.mqtt.com
|
||||
ip_address: 10.20.18.33
|
||||
- name: ipv6.example.com
|
||||
ip_address: 2001:db8:4455:6677::32
|
||||
hostsfile: hosts
|
||||
|
||||
logger:
|
||||
|
||||
ota:
|
||||
|
|
|
@ -9,6 +9,16 @@ bk72xx:
|
|||
esphome:
|
||||
name: bk72xx-test
|
||||
|
||||
network:
|
||||
hosts:
|
||||
- name: www.example.com
|
||||
ip_address: 192.168.1.80
|
||||
- name: www.mqtt.com
|
||||
ip_address: 10.20.18.33
|
||||
- name: ipv6.example.com
|
||||
ip_address: 2001:db8:4455:6677::32
|
||||
hostsfile: hosts
|
||||
|
||||
logger:
|
||||
|
||||
ota:
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components import network
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
|
||||
def _convert_mockobjs_to_string_literal(obj: Any) -> Any:
|
||||
"""Convert mock objects to their base objects."""
|
||||
if isinstance(obj, MockObj):
|
||||
return str(obj.base.args.args[0].string)
|
||||
if isinstance(obj, list):
|
||||
return [_convert_mockobjs_to_string_literal(x) for x in obj]
|
||||
if isinstance(obj, tuple):
|
||||
return tuple(_convert_mockobjs_to_string_literal(x) for x in obj)
|
||||
if isinstance(obj, dict):
|
||||
return {k: _convert_mockobjs_to_string_literal(v) for k, v in obj.items()}
|
||||
return obj
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("contents", "expected"),
|
||||
(
|
||||
("127.0.0.1 localhost", [("localhost", "127.0.0.1")]),
|
||||
(":1 localhost", [("localhost", ":1")]),
|
||||
("#192.168.1.1 commented", []),
|
||||
(
|
||||
"192.168.1.10 multi1.example.com multi1\n:1 localhost",
|
||||
[
|
||||
("multi1.example.com", "192.168.1.10"),
|
||||
("multi1", "192.168.1.10"),
|
||||
("localhost", ":1"),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_parse_hosts_file(contents: str, expected: list[tuple[str, str]]):
|
||||
"""Test parsing a hosts file."""
|
||||
actual = network.parse_hosts_file(contents)
|
||||
unmocked_actual = _convert_mockobjs_to_string_literal(actual)
|
||||
assert _convert_mockobjs_to_string_literal(unmocked_actual) == expected
|
Loading…
Reference in New Issue