tests
This commit is contained in:
parent
e037b0b102
commit
474f0f1a04
7 changed files with 67 additions and 27 deletions
2
Makefile
2
Makefile
|
@ -7,4 +7,4 @@ build:
|
||||||
run:
|
run:
|
||||||
cd entrypoint && dotnet run -- --user ${USER} --password ${PASSWORD} -H ${HOST}
|
cd entrypoint && dotnet run -- --user ${USER} --password ${PASSWORD} -H ${HOST}
|
||||||
test:
|
test:
|
||||||
dotnet test
|
cd tests && dotnet test
|
||||||
|
|
20
src/Cron.fs
20
src/Cron.fs
|
@ -8,7 +8,6 @@ open Pentole
|
||||||
open Datatypes
|
open Datatypes
|
||||||
|
|
||||||
open Pentole.String
|
open Pentole.String
|
||||||
open Pentole.Path
|
|
||||||
open Pentole.Map
|
open Pentole.Map
|
||||||
|
|
||||||
type User = string
|
type User = string
|
||||||
|
@ -23,13 +22,13 @@ let private parse_expr (now: Instant) text =
|
||||||
| true -> Ok c
|
| true -> Ok c
|
||||||
| false -> Error $"Can't parse cron expression: {text}"
|
| false -> Error $"Can't parse cron expression: {text}"
|
||||||
|
|
||||||
let to_pattern text =
|
let to_pattern (text: string) =
|
||||||
match Pentole.String.split " " text |> List.head with
|
match text with
|
||||||
| Prefix "@after" job -> After job |> Ok
|
| Prefix "@after" job -> After job |> Ok
|
||||||
| _ -> Error $"Can't parse as pattern: {text}"
|
| _ -> Error $"Can't parse as pattern: {text}"
|
||||||
|
|
||||||
match to_cron text, to_pattern text with
|
match to_cron text, to_pattern text with
|
||||||
| Error e, Error _ -> Error e
|
| Error e, Error p -> Error $"Can't parse {text} neither as pattern or cron expression"
|
||||||
| _, Ok p -> Ok (Pattern p)
|
| _, Ok p -> Ok (Pattern p)
|
||||||
| Ok cron_expr, _ ->
|
| Ok cron_expr, _ ->
|
||||||
(now.ToDateTimeOffset(), local_tz_net)
|
(now.ToDateTimeOffset(), local_tz_net)
|
||||||
|
@ -40,7 +39,7 @@ let private parse_expr (now: Instant) text =
|
||||||
else Instant.FromDateTimeOffset i.Value |> Cron |> Ok)
|
else Instant.FromDateTimeOffset i.Value |> Cron |> Ok)
|
||||||
|
|
||||||
|
|
||||||
let private parse now (db: Database.Requirements) =
|
let private parse now (db: Requirements) =
|
||||||
let when_ = parse_expr now db.``when``
|
let when_ = parse_expr now db.``when``
|
||||||
let env =
|
let env =
|
||||||
db.environment
|
db.environment
|
||||||
|
@ -52,7 +51,7 @@ let private parse now (db: Database.Requirements) =
|
||||||
else
|
else
|
||||||
Ok (splitted.[0], splitted.[1]))
|
Ok (splitted.[0], splitted.[1]))
|
||||||
|
|
||||||
let executable = Path.of_string db.executable |> Result.bind FileSystem.resolve
|
let executable = Which.which db.executable
|
||||||
let workdir = Path.of_string db.workdir |> Result.bind FileSystem.resolve
|
let workdir = Path.of_string db.workdir |> Result.bind FileSystem.resolve
|
||||||
|
|
||||||
let last_completed =
|
let last_completed =
|
||||||
|
@ -76,8 +75,7 @@ let private parse now (db: Database.Requirements) =
|
||||||
type JobKey = {
|
type JobKey = {
|
||||||
j: string; h: string
|
j: string; h: string
|
||||||
}
|
}
|
||||||
let sort_cron_jobs (now: Instant) (db_crons: Database.Requirements seq) =
|
let sort_cron_jobs (now: Instant) (db_crons: Requirements seq) =
|
||||||
|
|
||||||
|
|
||||||
let build_deps (lst: CronJob list) =
|
let build_deps (lst: CronJob list) =
|
||||||
let standalone, deps =
|
let standalone, deps =
|
||||||
|
@ -90,7 +88,7 @@ let sort_cron_jobs (now: Instant) (db_crons: Database.Requirements seq) =
|
||||||
in index, deps
|
in index, deps
|
||||||
in
|
in
|
||||||
let rec build_job_table acc (index: Map<JobKey, CronJob>) = function
|
let rec build_job_table acc (index: Map<JobKey, CronJob>) = function
|
||||||
| [] -> Ok acc
|
| [] -> Map.values acc |> Ok
|
||||||
| {when_=Cron _}::_ -> invalidOp "The jobs should have been partitioned"
|
| {when_=Cron _}::_ -> invalidOp "The jobs should have been partitioned"
|
||||||
| {when_=Pattern (After jb)} as x::xs ->
|
| {when_=Pattern (After jb)} as x::xs ->
|
||||||
let father = {j=jb; h=x.hostname}
|
let father = {j=jb; h=x.hostname}
|
||||||
|
@ -100,7 +98,7 @@ let sort_cron_jobs (now: Instant) (db_crons: Database.Requirements seq) =
|
||||||
Map.find father index |> Result.map List.singleton
|
Map.find father index |> Result.map List.singleton
|
||||||
| Some p -> Ok p
|
| Some p -> Ok p
|
||||||
if previous |> Result.isError then
|
if previous |> Result.isError then
|
||||||
Error "Invalid job definition. No such job_name {jb} in host {x.hostname}"
|
Error $"Invalid job definition. No such job_name {jb} in host {x.hostname}"
|
||||||
else
|
else
|
||||||
let p = Result.get previous
|
let p = Result.get previous
|
||||||
let acc' = Map.add father (x::p) acc
|
let acc' = Map.add father (x::p) acc
|
||||||
|
@ -109,4 +107,4 @@ let sort_cron_jobs (now: Instant) (db_crons: Database.Requirements seq) =
|
||||||
db_crons
|
db_crons
|
||||||
|> ResultList.collect (parse now)
|
|> ResultList.collect (parse now)
|
||||||
|> Result.map build_deps
|
|> Result.map build_deps
|
||||||
|> Result.map (fun (standalone, deps) -> build_job_table Map.empty standalone deps)
|
|> Result.bind (fun (standalone, deps) -> build_job_table Map.empty standalone deps)
|
||||||
|
|
|
@ -9,6 +9,8 @@ open Serilog
|
||||||
open Npgsql
|
open Npgsql
|
||||||
open Dapper
|
open Dapper
|
||||||
|
|
||||||
|
open Datatypes
|
||||||
|
|
||||||
let private connstring =
|
let private connstring =
|
||||||
let c = Environment.Environment()
|
let c = Environment.Environment()
|
||||||
$"Server={c.pg_host};Database={c.pg_dbname};" +
|
$"Server={c.pg_host};Database={c.pg_dbname};" +
|
||||||
|
@ -58,19 +60,6 @@ let make (logger: ILogger) =
|
||||||
let wait_notification (ct: CancellationToken) (db: t) =
|
let wait_notification (ct: CancellationToken) (db: t) =
|
||||||
db.connection.WaitAsync ct
|
db.connection.WaitAsync ct
|
||||||
|
|
||||||
[<CLIMutable>]
|
|
||||||
type Requirements = {
|
|
||||||
job_name: string
|
|
||||||
``when``: string
|
|
||||||
executable: string
|
|
||||||
user: string
|
|
||||||
workdir: string
|
|
||||||
hostname: string
|
|
||||||
args: string array
|
|
||||||
environment: string
|
|
||||||
done_at: System.DateTime option
|
|
||||||
}
|
|
||||||
|
|
||||||
let gather_requirements (hostname: string) (ct: CancellationToken) (db: t) =
|
let gather_requirements (hostname: string) (ct: CancellationToken) (db: t) =
|
||||||
let query = """select
|
let query = """select
|
||||||
c.job_name, c."when", c.executable, c.user, c.workdir, c.args, h.hostname,
|
c.job_name, c."when", c.executable, c.user, c.workdir, c.args, h.hostname,
|
||||||
|
|
|
@ -27,3 +27,16 @@ type CronJob = {
|
||||||
hostname: string
|
hostname: string
|
||||||
last_completed_at: Instant
|
last_completed_at: Instant
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[<CLIMutable>]
|
||||||
|
type Requirements = {
|
||||||
|
job_name: string
|
||||||
|
``when``: string
|
||||||
|
executable: string
|
||||||
|
user: string
|
||||||
|
workdir: string
|
||||||
|
hostname: string
|
||||||
|
args: string array
|
||||||
|
environment: string
|
||||||
|
done_at: System.DateTime option
|
||||||
|
}
|
||||||
|
|
|
@ -5,3 +5,6 @@ module Map =
|
||||||
match Map.tryFind key map with
|
match Map.tryFind key map with
|
||||||
| Some v -> Ok v
|
| Some v -> Ok v
|
||||||
| None -> Error $"Can't find key {key}"
|
| None -> Error $"Can't find key {key}"
|
||||||
|
|
||||||
|
let values (map: Map<'k, 'v>): 'v seq =
|
||||||
|
Map.values map
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<Compile Include="Map.fs" />
|
<Compile Include="Map.fs" />
|
||||||
<Compile Include="Result.fs" />
|
<Compile Include="Result.fs" />
|
||||||
<Compile Include="String.fs" />
|
<Compile Include="String.fs" />
|
||||||
|
<Compile Include="Which.fs" />
|
||||||
<Compile Include="Datatypes.fs" />
|
<Compile Include="Datatypes.fs" />
|
||||||
<Compile Include="Grains.fs" />
|
<Compile Include="Grains.fs" />
|
||||||
<Compile Include="DatabaseMigrations.fs" />
|
<Compile Include="DatabaseMigrations.fs" />
|
||||||
|
@ -38,6 +39,7 @@
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Sheller" Version="6.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -2,7 +2,42 @@ module tests
|
||||||
|
|
||||||
open NUnit.Framework
|
open NUnit.Framework
|
||||||
open Pentole.TestsExtensions
|
open Pentole.TestsExtensions
|
||||||
|
open Bidello.Datatypes
|
||||||
|
open Bidello
|
||||||
|
|
||||||
|
open Pentole.String
|
||||||
|
|
||||||
[<Test>]
|
[<Test>]
|
||||||
let Test1 () =
|
let string_prefix_active_pattern () =
|
||||||
Assert.Pass()
|
match "@after job" with
|
||||||
|
| Prefix "@after" j -> Assert.Pass ()
|
||||||
|
| _ -> Assert.Pass ()
|
||||||
|
|
||||||
|
match "@after job " with
|
||||||
|
| Prefix "@after" j -> Assert.Pass ()
|
||||||
|
| _ -> Assert.Pass ()
|
||||||
|
|
||||||
|
let bj =
|
||||||
|
{ job_name = "j1"
|
||||||
|
hostname = "h1"
|
||||||
|
``when`` = "* * * * *"
|
||||||
|
executable = "echo"
|
||||||
|
workdir = "/"; user = "nobody"
|
||||||
|
args = [||]; environment = "";
|
||||||
|
done_at = None }
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let job_deps () =
|
||||||
|
|
||||||
|
let requirements = [
|
||||||
|
bj
|
||||||
|
{bj with job_name = "j2"}
|
||||||
|
{bj with hostname = "h2"}
|
||||||
|
{bj with job_name = "j1_after"; ``when``="@after j1"}
|
||||||
|
// TODO: another test with this at h2
|
||||||
|
]
|
||||||
|
|
||||||
|
let now = NodaTime.SystemClock.Instance.GetCurrentInstant ()
|
||||||
|
let cjs = Cron.sort_cron_jobs now requirements
|
||||||
|
|
||||||
|
Assert.ok_is_equal Seq.empty cjs
|
||||||
|
|
Loading…
Reference in a new issue