Compare commits
2 commits
74e2da2f29
...
9c9f7cfa82
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c9f7cfa82 | |||
| 7a40326719 |
3 changed files with 476 additions and 73 deletions
|
|
@ -109,14 +109,15 @@ If **FILE** is **-**, this reads text from stdin instead.
|
||||||
The following is an example of a *config.toml* file:
|
The following is an example of a *config.toml* file:
|
||||||
|
|
||||||
```
|
```
|
||||||
auto=true # should `fwd` should enable identified ports
|
auto=true # should `fwd` should enable identified ports (default true)
|
||||||
|
|
||||||
[servers.foo] # Server-specific settings for foo
|
[servers.foo] # Server-specific settings for foo
|
||||||
auto=true
|
auto=true # defaults to the global setting
|
||||||
ports=[1080, 1082] # ports that are always present
|
ports=[1080, 1082] # ports that are always present
|
||||||
|
|
||||||
[servers.bar.ports] # `ports` can also be a table with port numbers as keys
|
[servers.bar.ports] # `ports` can also be a table with port numbers as keys
|
||||||
1080=true # the values can be booleans (for enabled)
|
1080=true # the values can be booleans (for enabled)...
|
||||||
|
1081="My program" # or strings (for descriptions).
|
||||||
|
|
||||||
[servers.bar.ports.1082] # port values can also be tables
|
[servers.bar.ports.1082] # port values can also be tables
|
||||||
enabled=true
|
enabled=true
|
||||||
|
|
|
||||||
128
release.py
Normal file
128
release.py
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
"""A script to automate building and uploading a release archive.
|
||||||
|
|
||||||
|
This is in python instead of bash because I abhor bash. Even though it's a
|
||||||
|
little nicer for running commands, it's worse at everything else.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
RELEASE_TAG = os.getenv("RELEASE_TAG")
|
||||||
|
|
||||||
|
BUILD = os.getenv("BUILD")
|
||||||
|
if BUILD is None:
|
||||||
|
raise Exception("you *must* set the BUILD environment variable")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class BuildSettings:
|
||||||
|
target: str
|
||||||
|
test: bool = True
|
||||||
|
man_page: bool = True
|
||||||
|
strip: bool = True
|
||||||
|
windows: bool = False
|
||||||
|
ext: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
print(f"doing release: {BUILD}")
|
||||||
|
build = {
|
||||||
|
"linux": BuildSettings(
|
||||||
|
target="x86_64-unknown-linux-musl",
|
||||||
|
),
|
||||||
|
"macos": BuildSettings(
|
||||||
|
target="x86_64-apple-darwin",
|
||||||
|
),
|
||||||
|
"arm-macos": BuildSettings(
|
||||||
|
target="aarch64-apple-darwin",
|
||||||
|
),
|
||||||
|
"windows": BuildSettings(
|
||||||
|
target="x86_64-pc-windows-msvc",
|
||||||
|
strip=False,
|
||||||
|
man_page=False,
|
||||||
|
windows=True,
|
||||||
|
ext=".exe",
|
||||||
|
),
|
||||||
|
}[BUILD]
|
||||||
|
|
||||||
|
print(f"settings: {build}")
|
||||||
|
|
||||||
|
|
||||||
|
target_dir = pathlib.Path("target") / build.target / "release"
|
||||||
|
bins = [(target_dir / bin).with_suffix(build.ext) for bin in ["fwd", "fwd-browse"]]
|
||||||
|
|
||||||
|
|
||||||
|
def build_and_test(staging: pathlib.Path):
|
||||||
|
# Tools
|
||||||
|
subprocess.run(
|
||||||
|
["rustup", "target", "add", build.target],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test...?
|
||||||
|
if build.test:
|
||||||
|
subprocess.run(
|
||||||
|
["cargo", "test", "--verbose", "--release", "--target", build.target],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build
|
||||||
|
subprocess.run(
|
||||||
|
["cargo", "build", "--verbose", "--release", "--target", build.target],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Strip
|
||||||
|
if build.strip:
|
||||||
|
for bin in bins:
|
||||||
|
subprocess.run(["strip", bin], check=True)
|
||||||
|
|
||||||
|
# Copy
|
||||||
|
for bin in bins:
|
||||||
|
shutil.copyfile(bin, os.path.join(staging, os.path.basename(bin)))
|
||||||
|
|
||||||
|
|
||||||
|
def build_docs(staging: pathlib.Path):
|
||||||
|
shutil.copyfile("README.md", staging / "README.md")
|
||||||
|
if build.man_page:
|
||||||
|
print("Creating man page...")
|
||||||
|
proc = subprocess.run(
|
||||||
|
["pandoc", "-s", "-tman", os.path.join("doc", "fwd.man.md")],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
encoding="utf8",
|
||||||
|
)
|
||||||
|
contents = proc.stdout
|
||||||
|
with open(staging / "fwd.1", "w", encoding="utf-8") as f:
|
||||||
|
f.write(contents)
|
||||||
|
|
||||||
|
|
||||||
|
staging = pathlib.Path(f"fwd-{build.target}")
|
||||||
|
os.makedirs(staging, exist_ok=True)
|
||||||
|
|
||||||
|
build_and_test(staging)
|
||||||
|
build_docs(staging)
|
||||||
|
|
||||||
|
print("Creating archive...")
|
||||||
|
if build.windows:
|
||||||
|
archive = f"{staging}.zip"
|
||||||
|
subprocess.run(["7z", "a", archive, f"{staging}"], check=True)
|
||||||
|
else:
|
||||||
|
archive = f"{staging}.tar.gz"
|
||||||
|
subprocess.run(["tar", "czf", archive, f"{staging}"], check=True)
|
||||||
|
|
||||||
|
shutil.rmtree(staging)
|
||||||
|
|
||||||
|
if RELEASE_TAG is None:
|
||||||
|
print("Not releasing to github, RELEASE_TAG is none.")
|
||||||
|
else:
|
||||||
|
print(f"Uploading {archive} to github release {RELEASE_TAG}...")
|
||||||
|
subprocess.run(
|
||||||
|
["gh", "release", "upload", RELEASE_TAG, archive, "--clobber"],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.unlink(archive)
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use std::collections::hash_map;
|
use std::collections::hash_map;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use toml::Value;
|
use toml::value::{Table, Value};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
pub struct PortConfig {
|
pub struct PortConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
auto: bool,
|
auto: bool,
|
||||||
ports: HashMap<u16, PortConfig>,
|
ports: HashMap<u16, PortConfig>,
|
||||||
|
|
@ -45,6 +47,7 @@ impl ServerConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
auto: bool,
|
auto: bool,
|
||||||
servers: HashMap<String, ServerConfig>,
|
servers: HashMap<String, ServerConfig>,
|
||||||
|
|
@ -85,85 +88,101 @@ fn default() -> Config {
|
||||||
Config { auto: true, servers: HashMap::new() }
|
Config { auto: true, servers: HashMap::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_config(value: &Value) -> Result<Config> {
|
fn get_bool(table: &Table, key: &str, default: bool) -> Result<bool> {
|
||||||
match value {
|
match table.get(key) {
|
||||||
Value::Table(table) => Ok({
|
None => Ok(default),
|
||||||
let auto = match table.get("auto") {
|
Some(Value::Boolean(v)) => Ok(*v),
|
||||||
None => true,
|
Some(v) => bail!("expected a true or false, got {v:?}"),
|
||||||
Some(Value::Boolean(v)) => *v,
|
|
||||||
Some(v) => bail!("expected a true or false, got {:?}", v),
|
|
||||||
};
|
|
||||||
Config { auto, servers: get_servers(table, auto)? }
|
|
||||||
}),
|
|
||||||
_ => bail!("top level must be a table"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_servers(
|
fn parse_config(value: &Value) -> Result<Config> {
|
||||||
table: &toml::value::Table,
|
let Value::Table(table) = value else {
|
||||||
|
bail!("top level must be a table")
|
||||||
|
};
|
||||||
|
|
||||||
|
let auto = get_bool(table, "auto", true)?;
|
||||||
|
let servers = match table.get("servers") {
|
||||||
|
None => &Table::new(),
|
||||||
|
Some(Value::Table(t)) => t,
|
||||||
|
Some(v) => bail!("Expected a table in the servers key, got {v:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
auto,
|
||||||
|
servers: parse_servers(servers, auto)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_servers(
|
||||||
|
table: &Table,
|
||||||
auto: bool,
|
auto: bool,
|
||||||
) -> Result<HashMap<String, ServerConfig>> {
|
) -> Result<HashMap<String, ServerConfig>> {
|
||||||
match table.get("servers") {
|
let mut servers = HashMap::new();
|
||||||
None => Ok(HashMap::new()),
|
for (k, v) in table {
|
||||||
Some(Value::Table(table)) => Ok({
|
let Value::Table(table) = v else {
|
||||||
let mut servers = HashMap::new();
|
bail!("expected a table for server {k}, got {v:?}");
|
||||||
for (k, v) in table {
|
};
|
||||||
servers.insert(k.clone(), get_server(v, auto)?);
|
|
||||||
}
|
servers.insert(k.clone(), parse_server(table, auto)?);
|
||||||
servers
|
|
||||||
}),
|
|
||||||
v => bail!("expected a table in the servers key, got {:?}", v),
|
|
||||||
}
|
}
|
||||||
|
Ok(servers)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_server(value: &Value, auto: bool) -> Result<ServerConfig> {
|
fn parse_server(table: &Table, auto: bool) -> Result<ServerConfig> {
|
||||||
|
let auto = get_bool(table, "auto", auto)?;
|
||||||
|
let ports = match table.get("ports") {
|
||||||
|
None => HashMap::new(),
|
||||||
|
Some(v) => parse_ports(v)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ServerConfig { auto, ports })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ports(value: &Value) -> Result<HashMap<u16, PortConfig>> {
|
||||||
match value {
|
match value {
|
||||||
Value::Table(table) => Ok(ServerConfig {
|
Value::Array(array) => {
|
||||||
auto: match table.get("auto") {
|
|
||||||
None => auto, // Default to global default
|
|
||||||
Some(Value::Boolean(v)) => *v,
|
|
||||||
Some(v) => bail!("expected true or false, got {:?}", v),
|
|
||||||
},
|
|
||||||
ports: get_ports(table)?,
|
|
||||||
}),
|
|
||||||
value => bail!("expected a table, got {:?}", value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_ports(table: &toml::value::Table) -> Result<HashMap<u16, PortConfig>> {
|
|
||||||
match table.get("ports") {
|
|
||||||
None => Ok(HashMap::new()),
|
|
||||||
Some(Value::Table(table)) => Ok({
|
|
||||||
let mut ports = HashMap::new();
|
|
||||||
for (k,v) in table {
|
|
||||||
let port:u16 = k.parse()?;
|
|
||||||
let config = match v {
|
|
||||||
Value::Boolean(enabled) => PortConfig{enabled:*enabled, description:None},
|
|
||||||
Value::Table(table) => PortConfig{
|
|
||||||
enabled: match table.get("enabled") {
|
|
||||||
Some(Value::Boolean(enabled)) => *enabled,
|
|
||||||
_ => bail!("not implemented"),
|
|
||||||
},
|
|
||||||
description: match table.get("description") {
|
|
||||||
Some(Value::String(desc)) => Some(desc.clone()),
|
|
||||||
Some(v) => bail!("expect a string description, got {:?}", v),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_ => bail!("expected either a boolean (enabled) or a table for a port config, got {:?}", v),
|
|
||||||
};
|
|
||||||
ports.insert(port, config);
|
|
||||||
}
|
|
||||||
ports
|
|
||||||
}),
|
|
||||||
Some(Value::Array(array)) => Ok({
|
|
||||||
let mut ports = HashMap::new();
|
let mut ports = HashMap::new();
|
||||||
for v in array {
|
for v in array {
|
||||||
ports.insert(get_port_number(v)?, PortConfig{enabled:true, description:None});
|
ports.insert(
|
||||||
|
get_port_number(v)?,
|
||||||
|
PortConfig { enabled: true, description: None },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ports
|
Ok(ports)
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Table(table) => {
|
||||||
|
let mut ports = HashMap::new();
|
||||||
|
for (k, v) in table {
|
||||||
|
let port: u16 = k.parse()?;
|
||||||
|
let config = parse_port_config(v)?;
|
||||||
|
ports.insert(port, config);
|
||||||
|
}
|
||||||
|
Ok(ports)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => bail!("ports must be either an array or a table, got {value:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_port_config(value: &Value) -> Result<PortConfig> {
|
||||||
|
match value {
|
||||||
|
Value::Boolean(enabled) => Ok(PortConfig{enabled:*enabled, description:None}),
|
||||||
|
Value::String(description) => Ok(PortConfig{
|
||||||
|
enabled: true,
|
||||||
|
description: Some(description.clone()),
|
||||||
}),
|
}),
|
||||||
Some(v) => bail!("ports must be either a table of '<port> = ...' or an array of ports, got {:?}", v),
|
Value::Table(table) => {
|
||||||
|
let enabled = get_bool(table, "enabled", true)?;
|
||||||
|
let description = match table.get("description") {
|
||||||
|
Some(Value::String(desc)) => Some(desc.clone()),
|
||||||
|
Some(v) => bail!("expect a string description, got {v:?}"),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
Ok(PortConfig { enabled, description })
|
||||||
|
},
|
||||||
|
_ => bail!("expected either a boolean (enabled), a string (description), or a table for a port config, got {value:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,3 +193,258 @@ fn get_port_number(v: &Value) -> Result<u16> {
|
||||||
};
|
};
|
||||||
Ok(port)
|
Ok(port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
fn config_test(config: &str, expected: Config) {
|
||||||
|
let config = config.parse::<Value>().expect("case not toml");
|
||||||
|
let config = parse_config(&config).expect("unable to parse config");
|
||||||
|
|
||||||
|
assert_eq!(expected, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_error_test(config: &str) {
|
||||||
|
let config = config.parse::<Value>().expect("case not toml");
|
||||||
|
assert!(parse_config(&config).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
config_test("", Config { auto: true, servers: HashMap::new() });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_false() {
|
||||||
|
config_test(
|
||||||
|
"
|
||||||
|
auto=false
|
||||||
|
",
|
||||||
|
Config { auto: false, servers: HashMap::new() },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_not_boolean() {
|
||||||
|
config_error_test(
|
||||||
|
"
|
||||||
|
auto='what is going on'
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn servers_not_table() {
|
||||||
|
config_error_test("servers=1234");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn servers_default() {
|
||||||
|
config_test("servers.foo={}", {
|
||||||
|
let mut servers = HashMap::new();
|
||||||
|
servers.insert(
|
||||||
|
"foo".to_string(),
|
||||||
|
ServerConfig { auto: true, ports: HashMap::new() },
|
||||||
|
);
|
||||||
|
Config { auto: true, servers }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn servers_auto_false() {
|
||||||
|
config_test(
|
||||||
|
"
|
||||||
|
[servers.foo]
|
||||||
|
auto=false
|
||||||
|
",
|
||||||
|
{
|
||||||
|
let mut servers = HashMap::new();
|
||||||
|
servers.insert(
|
||||||
|
"foo".to_string(),
|
||||||
|
ServerConfig { auto: false, ports: HashMap::new() },
|
||||||
|
);
|
||||||
|
Config { auto: true, servers }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn servers_auto_not_bool() {
|
||||||
|
config_error_test(
|
||||||
|
"
|
||||||
|
[servers.foo]
|
||||||
|
auto=1234
|
||||||
|
",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn servers_ports_list() {
|
||||||
|
config_test(
|
||||||
|
"
|
||||||
|
[servers.foo]
|
||||||
|
ports=[1,2,3]
|
||||||
|
",
|
||||||
|
{
|
||||||
|
let mut servers = HashMap::new();
|
||||||
|
servers.insert(
|
||||||
|
"foo".to_string(),
|
||||||
|
ServerConfig {
|
||||||
|
auto: true,
|
||||||
|
ports: {
|
||||||
|
let mut ports = HashMap::new();
|
||||||
|
ports.insert(
|
||||||
|
1,
|
||||||
|
PortConfig { enabled: true, description: None },
|
||||||
|
);
|
||||||
|
ports.insert(
|
||||||
|
2,
|
||||||
|
PortConfig { enabled: true, description: None },
|
||||||
|
);
|
||||||
|
ports.insert(
|
||||||
|
3,
|
||||||
|
PortConfig { enabled: true, description: None },
|
||||||
|
);
|
||||||
|
ports
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Config { auto: true, servers }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn servers_ports_table_variations() {
|
||||||
|
config_test(
|
||||||
|
"
|
||||||
|
[servers.foo.ports]
|
||||||
|
1=true
|
||||||
|
2={enabled=false}
|
||||||
|
3=false
|
||||||
|
",
|
||||||
|
{
|
||||||
|
let mut servers = HashMap::new();
|
||||||
|
servers.insert(
|
||||||
|
"foo".to_string(),
|
||||||
|
ServerConfig {
|
||||||
|
auto: true,
|
||||||
|
ports: {
|
||||||
|
let mut ports = HashMap::new();
|
||||||
|
ports.insert(
|
||||||
|
1,
|
||||||
|
PortConfig { enabled: true, description: None },
|
||||||
|
);
|
||||||
|
ports.insert(
|
||||||
|
2,
|
||||||
|
PortConfig {
|
||||||
|
enabled: false,
|
||||||
|
description: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ports.insert(
|
||||||
|
3,
|
||||||
|
PortConfig {
|
||||||
|
enabled: false,
|
||||||
|
description: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ports
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Config { auto: true, servers }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn servers_ports_table_descriptions() {
|
||||||
|
config_test(
|
||||||
|
"
|
||||||
|
[servers.foo.ports]
|
||||||
|
1={enabled=false}
|
||||||
|
2={description='humble'}
|
||||||
|
",
|
||||||
|
{
|
||||||
|
let mut servers = HashMap::new();
|
||||||
|
servers.insert(
|
||||||
|
"foo".to_string(),
|
||||||
|
ServerConfig {
|
||||||
|
auto: true,
|
||||||
|
ports: {
|
||||||
|
let mut ports = HashMap::new();
|
||||||
|
ports.insert(
|
||||||
|
1,
|
||||||
|
PortConfig {
|
||||||
|
enabled: false,
|
||||||
|
description: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ports.insert(
|
||||||
|
2,
|
||||||
|
PortConfig {
|
||||||
|
enabled: true,
|
||||||
|
description: Some("humble".to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ports
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Config { auto: true, servers }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn servers_ports_raw_desc() {
|
||||||
|
config_test(
|
||||||
|
"
|
||||||
|
[servers.foo.ports]
|
||||||
|
1='humble'
|
||||||
|
",
|
||||||
|
{
|
||||||
|
let mut servers = HashMap::new();
|
||||||
|
servers.insert(
|
||||||
|
"foo".to_string(),
|
||||||
|
ServerConfig {
|
||||||
|
auto: true,
|
||||||
|
ports: {
|
||||||
|
let mut ports = HashMap::new();
|
||||||
|
ports.insert(
|
||||||
|
1,
|
||||||
|
PortConfig {
|
||||||
|
enabled: true,
|
||||||
|
description: Some("humble".to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ports
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Config { auto: true, servers }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn servers_inherit_auto() {
|
||||||
|
config_test(
|
||||||
|
"
|
||||||
|
auto=false
|
||||||
|
servers.foo={}
|
||||||
|
",
|
||||||
|
{
|
||||||
|
let mut servers = HashMap::new();
|
||||||
|
servers.insert(
|
||||||
|
"foo".to_string(),
|
||||||
|
ServerConfig { auto: false, ports: HashMap::new() },
|
||||||
|
);
|
||||||
|
Config { auto: false, servers }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue