diff --git a/src/Fable.Python.fsproj b/src/Fable.Python.fsproj index b446621..b524b01 100644 --- a/src/Fable.Python.fsproj +++ b/src/Fable.Python.fsproj @@ -19,7 +19,9 @@ + + diff --git a/src/stdlib/Builtins.fs b/src/stdlib/Builtins.fs index 6dcb665..4ec2c42 100644 --- a/src/stdlib/Builtins.fs +++ b/src/stdlib/Builtins.fs @@ -200,6 +200,10 @@ type IExports = abstract int: obj -> int /// Object to float abstract float: obj -> float + /// Convert to bytes + abstract bytes: byte[] -> byte[] + /// Convert string to bytes with encoding + abstract bytes: string * encoding: string -> byte[] /// Return the largest item in an iterable or the largest of two or more arguments. abstract max: 'T * 'T -> 'T diff --git a/src/stdlib/Logging.fs b/src/stdlib/Logging.fs new file mode 100644 index 0000000..5dde395 --- /dev/null +++ b/src/stdlib/Logging.fs @@ -0,0 +1,148 @@ +/// Type bindings for Python logging module: https://docs.python.org/3/library/logging.html +module Fable.Python.Logging + +open Fable.Core + +// fsharplint:disable MemberNames + +/// Logging levels +[] +module Level = + [] + let CRITICAL = 50 + + [] + let FATAL = 50 + + [] + let ERROR = 40 + + [] + let WARNING = 30 + + [] + let WARN = 30 + + [] + let INFO = 20 + + [] + let DEBUG = 10 + + [] + let NOTSET = 0 + +/// Formatter for log records +[] +type Formatter(fmt: string, ?datefmt: string) = + member _.format(record: obj) : string = nativeOnly + +/// Handler base type +[] +type Handler() = + /// Set the logging level + member _.setLevel(level: int) : unit = nativeOnly + /// Set the formatter for this handler + member _.setFormatter(formatter: Formatter) : unit = nativeOnly + +/// StreamHandler - logs to a stream (stderr by default) +[] +type StreamHandler(?stream: obj) = + inherit Handler() + +/// FileHandler - logs to a file +[] +type FileHandler(filename: string, ?mode: string) = + inherit Handler() + +/// Logger instance +[] +type Logger(name: string, ?level: int) = + /// Log a message with severity DEBUG + member _.debug(msg: string) : unit = nativeOnly + /// Log a message with severity INFO + member _.info(msg: string) : unit = nativeOnly + /// Log a message with severity WARNING + member _.warning(msg: string) : unit = nativeOnly + /// Log a message with severity ERROR + member _.error(msg: string) : unit = nativeOnly + /// Log a message with severity CRITICAL + member _.critical(msg: string) : unit = nativeOnly + /// Log a message with severity ERROR and exception info + member _.``exception``(msg: string) : unit = nativeOnly + /// Log a message with the specified level + [] + member _.log(level: int, msg: string) : unit = nativeOnly + /// Set the logging level + [] + member _.setLevel(level: int) : unit = nativeOnly + /// Get the effective logging level + [] + member _.getEffectiveLevel() : int = nativeOnly + /// Check if the logger is enabled for the specified level + [] + member _.isEnabledFor(level: int) : bool = nativeOnly + /// Add the specified handler to this logger + [] + member _.addHandler(handler: Handler) : unit = nativeOnly + /// Remove the specified handler from this logger + [] + member _.removeHandler(handler: Handler) : unit = nativeOnly + /// Check if this logger has any handlers configured + [] + member _.hasHandlers() : bool = nativeOnly + /// The name of the logger + member _.name: string = nativeOnly + +[] +type IExports = + /// Log a message with severity DEBUG on the root logger + abstract debug: msg: string -> unit + /// Log a message with severity INFO on the root logger + abstract info: msg: string -> unit + /// Log a message with severity WARNING on the root logger + abstract warning: msg: string -> unit + /// Log a message with severity ERROR on the root logger + abstract error: msg: string -> unit + /// Log a message with severity CRITICAL on the root logger + abstract critical: msg: string -> unit + /// Log a message with severity ERROR and exception info on the root logger + abstract ``exception``: msg: string -> unit + /// Log a message with the specified level on the root logger + abstract log: level: int * msg: string -> unit + + /// Return a logger with the specified name + [] + abstract getLogger: name: string -> Logger + /// Return the root logger + [] + abstract getLogger: unit -> Logger + + /// Do basic configuration for the logging system + [] + [] + abstract basicConfig: + ?filename: string * + ?filemode: string * + ?format: string * + ?datefmt: string * + ?style: string * + ?level: int * + ?stream: obj * + ?force: bool -> + unit + + /// Disable all logging calls of severity level and below + [] + abstract disable: level: int -> unit + + /// Logging level constants + abstract DEBUG: int + abstract INFO: int + abstract WARNING: int + abstract ERROR: int + abstract CRITICAL: int + +/// Logging facility for Python +[] +let logging: IExports = nativeOnly diff --git a/src/stdlib/Random.fs b/src/stdlib/Random.fs new file mode 100644 index 0000000..ae92219 --- /dev/null +++ b/src/stdlib/Random.fs @@ -0,0 +1,144 @@ +/// Type bindings for Python random module: https://docs.python.org/3/library/random.html +module Fable.Python.Random + +open System.Collections.Generic +open Fable.Core + +// fsharplint:disable MemberNames + +[] +type IExports = + /// Initialize the random number generator + /// See https://docs.python.org/3/library/random.html#random.seed + [] + abstract seed: a: int -> unit + /// Initialize the random number generator + /// See https://docs.python.org/3/library/random.html#random.seed + abstract seed: a: nativeint -> unit + /// Initialize the random number generator + /// See https://docs.python.org/3/library/random.html#random.seed + abstract seed: a: float -> unit + /// Initialize the random number generator + /// See https://docs.python.org/3/library/random.html#random.seed + abstract seed: a: string -> unit + /// Initialize the random number generator with current time + /// See https://docs.python.org/3/library/random.html#random.seed + abstract seed: unit -> unit + + /// Return a random floating point number in the range [0.0, 1.0) + /// See https://docs.python.org/3/library/random.html#random.random + abstract random: unit -> float + + /// Return a random floating point number N such that a <= N <= b + /// See https://docs.python.org/3/library/random.html#random.uniform + abstract uniform: a: float * b: float -> float + + /// Return a random floating point number N such that low <= N <= high with triangular distribution + /// See https://docs.python.org/3/library/random.html#random.triangular + abstract triangular: low: float * high: float * mode: float -> float + /// Return a random floating point number N such that 0 <= N <= 1 with triangular distribution + /// See https://docs.python.org/3/library/random.html#random.triangular + abstract triangular: unit -> float + + /// Return a random integer N such that a <= N <= b + /// See https://docs.python.org/3/library/random.html#random.randint + abstract randint: a: int * b: int -> int + + /// Return a randomly selected element from range(start, stop, step) + /// See https://docs.python.org/3/library/random.html#random.randrange + abstract randrange: stop: int -> int + /// Return a randomly selected element from range(start, stop, step) + /// See https://docs.python.org/3/library/random.html#random.randrange + abstract randrange: start: int * stop: int -> int + /// Return a randomly selected element from range(start, stop, step) + /// See https://docs.python.org/3/library/random.html#random.randrange + abstract randrange: start: int * stop: int * step: int -> int + + /// Return a random element from the non-empty sequence + /// See https://docs.python.org/3/library/random.html#random.choice + [] + abstract choice: seq: 'T[] -> 'T + /// Return a random element from the non-empty sequence + /// See https://docs.python.org/3/library/random.html#random.choice + [] + abstract choice: seq: 'T list -> 'T + /// Return a random element from the non-empty sequence + /// See https://docs.python.org/3/library/random.html#random.choice + abstract choice: seq: ResizeArray<'T> -> 'T + + /// Return a k length list of unique elements chosen from the population sequence + /// See https://docs.python.org/3/library/random.html#random.sample + [] + abstract sample: population: 'T[] * k: int -> ResizeArray<'T> + /// Return a k length list of unique elements chosen from the population sequence + /// See https://docs.python.org/3/library/random.html#random.sample + [] + abstract sample: population: 'T list * k: int -> ResizeArray<'T> + /// Return a k length list of unique elements chosen from the population sequence + /// See https://docs.python.org/3/library/random.html#random.sample + [] + abstract sample: population: ResizeArray<'T> * k: int -> ResizeArray<'T> + + /// Return a k sized list of elements chosen from the population with replacement + /// See https://docs.python.org/3/library/random.html#random.choices + [] + abstract choices: population: 'T[] * k: int -> ResizeArray<'T> + /// Return a k sized list of elements chosen from the population with replacement + /// See https://docs.python.org/3/library/random.html#random.choices + [] + abstract choices: population: 'T list * k: int -> ResizeArray<'T> + /// Return a k sized list of elements chosen from the population with replacement + /// See https://docs.python.org/3/library/random.html#random.choices + [] + abstract choices: population: ResizeArray<'T> * k: int -> ResizeArray<'T> + + /// Shuffle the sequence x in place + /// See https://docs.python.org/3/library/random.html#random.shuffle + abstract shuffle: x: 'T[] -> unit + /// Shuffle the sequence x in place + /// See https://docs.python.org/3/library/random.html#random.shuffle + abstract shuffle: x: ResizeArray<'T> -> unit + + /// Return a random integer with k random bits + /// See https://docs.python.org/3/library/random.html#random.getrandbits + abstract getrandbits: k: int -> int + + /// Beta distribution + /// See https://docs.python.org/3/library/random.html#random.betavariate + abstract betavariate: alpha: float * beta: float -> float + + /// Exponential distribution + /// See https://docs.python.org/3/library/random.html#random.expovariate + abstract expovariate: lambd: float -> float + + /// Gamma distribution + /// See https://docs.python.org/3/library/random.html#random.gammavariate + abstract gammavariate: alpha: float * beta: float -> float + + /// Gaussian distribution (same as normalvariate but faster) + /// See https://docs.python.org/3/library/random.html#random.gauss + abstract gauss: mu: float * sigma: float -> float + + /// Log normal distribution + /// See https://docs.python.org/3/library/random.html#random.lognormvariate + abstract lognormvariate: mu: float * sigma: float -> float + + /// Normal distribution + /// See https://docs.python.org/3/library/random.html#random.normalvariate + abstract normalvariate: mu: float * sigma: float -> float + + /// Von Mises distribution + /// See https://docs.python.org/3/library/random.html#random.vonmisesvariate + abstract vonmisesvariate: mu: float * kappa: float -> float + + /// Pareto distribution + /// See https://docs.python.org/3/library/random.html#random.paretovariate + abstract paretovariate: alpha: float -> float + + /// Weibull distribution + /// See https://docs.python.org/3/library/random.html#random.weibullvariate + abstract weibullvariate: alpha: float * beta: float -> float + +/// Random variable generators +[] +let random: IExports = nativeOnly diff --git a/src/stdlib/String.fs b/src/stdlib/String.fs index 0136dc8..9bf2166 100644 --- a/src/stdlib/String.fs +++ b/src/stdlib/String.fs @@ -1,4 +1,4 @@ -/// Type bindings for Python string operations: https://docs.python.org/3/library/stdtypes.html#string-methods +/// Type bindings for Python string module: https://docs.python.org/3/library/string.html module Fable.Python.String open System @@ -10,3 +10,64 @@ type System.String with [] member _.format([] args: Object[]) = nativeOnly + +/// Template class for $-based string substitution +[] +type Template(template: string) = + /// The template string passed to the constructor + member _.template: string = nativeOnly + + /// Perform substitution, returning a new string + /// Raises KeyError if placeholders are missing from mapping + [] + member _.substitute(mapping: obj) : string = nativeOnly + + /// Perform substitution using keyword arguments + [] + member _.substituteKw(kwargs: obj) : string = nativeOnly + + /// Like substitute(), but returns original placeholder if missing + [] + member _.safe_substitute(mapping: obj) : string = nativeOnly + + /// Like substitute(), but returns original placeholder if missing (keyword args) + [] + member _.safe_substituteKw(kwargs: obj) : string = nativeOnly + + /// Returns False if the template has invalid placeholders + [] + member _.is_valid() : bool = nativeOnly + + /// Returns a list of valid identifiers in the template + [] + member _.get_identifiers() : ResizeArray = nativeOnly + +[] +type IExports = + /// The concatenation of ascii_lowercase and ascii_uppercase + abstract ascii_letters: string + /// The lowercase letters 'abcdefghijklmnopqrstuvwxyz' + abstract ascii_lowercase: string + /// The uppercase letters 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + abstract ascii_uppercase: string + /// The string '0123456789' + abstract digits: string + /// The string '0123456789abcdefABCDEF' + abstract hexdigits: string + /// The string '01234567' + abstract octdigits: string + /// String of ASCII characters considered punctuation + abstract punctuation: string + /// String of ASCII characters considered printable + abstract printable: string + /// String containing all ASCII whitespace characters + abstract whitespace: string + + /// Split the argument into words, capitalize each word, and join them + abstract capwords: s: string -> string + /// Split the argument into words using sep, capitalize each word, and join them + abstract capwords: s: string * sep: string -> string + +/// Python string module +[] +let pyString: IExports = nativeOnly diff --git a/src/stdlib/Sys.fs b/src/stdlib/Sys.fs index 23d6c76..1558e01 100644 --- a/src/stdlib/Sys.fs +++ b/src/stdlib/Sys.fs @@ -28,7 +28,7 @@ type VersionInfo = [] type IExports = /// Command line arguments passed to the Python script - abstract argv: string array + abstract argv: ResizeArray /// Native byte order (endianness) of the system abstract byteorder: ByteOrder /// Version information encoded as a single integer @@ -38,7 +38,7 @@ type IExports = /// Maximum Unicode code point value abstract maxunicode: int /// Module search path - list of directory names where Python looks for modules - abstract path: string array + abstract path: ResizeArray /// Platform identifier string abstract platform: string /// Site-specific directory prefix where platform-independent Python files are installed @@ -52,7 +52,11 @@ type IExports = abstract exit: unit -> 'a /// Exits with provided status /// See https://docs.python.org/3/library/sys.html#sys.exit + [] abstract exit: status: int -> 'a + /// Exits with provided status + /// See https://docs.python.org/3/library/sys.html#sys.exit + abstract exit: status: nativeint -> 'a /// Exits with exit status 1, printing message to stderr /// See https://docs.python.org/3/library/sys.html#sys.exit abstract exit: message: string -> 'a diff --git a/test/Fable.Python.Test.fsproj b/test/Fable.Python.Test.fsproj index 88f4860..50dc0bd 100644 --- a/test/Fable.Python.Test.fsproj +++ b/test/Fable.Python.Test.fsproj @@ -18,6 +18,10 @@ + + + + diff --git a/test/TestAsyncIO.fs b/test/TestAsyncIO.fs index 843a8a1..0f73981 100644 --- a/test/TestAsyncIO.fs +++ b/test/TestAsyncIO.fs @@ -29,5 +29,81 @@ let ``test sleep works`` () = [] let ``test sleep with value works`` () = let tsk = task { return! asyncio.create_task (asyncio.sleep (0.1, 42)) } - let result = asyncio.run (tsk) + let result = asyncio.run tsk result |> equal 42 + +[] +let ``test multiple awaits work`` () = + let tsk = + task { + let! a = asyncio.create_task (asyncio.sleep (0.01, 10)) + let! b = asyncio.create_task (asyncio.sleep (0.01, 20)) + let! c = asyncio.create_task (asyncio.sleep (0.01, 12)) + return a + b + c + } + + let result = asyncio.run tsk + result |> equal 42 + +[] +let ``test nested tasks work`` () = + let inner () = + task { + do! asyncio.create_task (asyncio.sleep 0.01) + return 21 + } + + let outer = + task { + let! x = inner () + let! y = inner () + return x + y + } + + let result = asyncio.run outer + result |> equal 42 + +[] +let ``test get_running_loop works`` () = + let tsk = + task { + let loop = asyncio.get_running_loop () + return loop <> null + } + + let result = asyncio.run tsk + result |> equal true + +[] +let ``test task with string result works`` () = + let tsk = + task { + let! result = asyncio.create_task (asyncio.sleep (0.01, "hello")) + return result + " world" + } + + let result = asyncio.run tsk + result |> equal "hello world" + +[] +let ``test task with list result works`` () = + let tsk = + task { + let! a = asyncio.create_task (asyncio.sleep (0.01, [ 1; 2 ])) + let! b = asyncio.create_task (asyncio.sleep (0.01, [ 3; 4 ])) + return a @ b + } + + let result = asyncio.run tsk + result |> equal [ 1; 2; 3; 4 ] + +[] +let ``test task with option result works`` () = + let tsk = + task { + let! a = asyncio.create_task (asyncio.sleep (0.01, Some 42)) + return a + } + + let result = asyncio.run tsk + result |> equal (Some 42) diff --git a/test/TestBase64.fs b/test/TestBase64.fs new file mode 100644 index 0000000..05e0686 --- /dev/null +++ b/test/TestBase64.fs @@ -0,0 +1,87 @@ +module Fable.Python.Tests.Base64 + +open Util.Testing +open Fable.Python.Base64 +open Fable.Python.Builtins + +[] +let ``test b64encode works`` () = + let input = builtins.bytes "Hello, World!"B + let result = base64.b64encode input + result |> equal (builtins.bytes "SGVsbG8sIFdvcmxkIQ=="B) + +[] +let ``test b64decode from bytes works`` () = + let encoded = builtins.bytes "SGVsbG8sIFdvcmxkIQ=="B + let result = base64.b64decode encoded + result |> equal (builtins.bytes "Hello, World!"B) + +[] +let ``test b64decode from string works`` () = + let encoded = "SGVsbG8sIFdvcmxkIQ==" + let result = base64.b64decode encoded + result |> equal (builtins.bytes "Hello, World!"B) + +[] +let ``test roundtrip b64encode and b64decode works`` () = + let original = builtins.bytes "Test data 123!@#"B + let encoded = base64.b64encode original + let decoded = base64.b64decode encoded + decoded |> equal original + +[] +let ``test standard_b64encode works`` () = + let input = builtins.bytes "Hello"B + let result = base64.standard_b64encode input + result |> equal (builtins.bytes "SGVsbG8="B) + +[] +let ``test standard_b64decode from string works`` () = + let encoded = "SGVsbG8=" + let result = base64.standard_b64decode encoded + result |> equal (builtins.bytes "Hello"B) + +[] +let ``test standard_b64decode from bytes works`` () = + let encoded = builtins.bytes "SGVsbG8="B + let result = base64.standard_b64decode encoded + result |> equal (builtins.bytes "Hello"B) + +[] +let ``test urlsafe_b64encode works`` () = + // Using bytes that produce + and / in standard base64 + let input = builtins.bytes [| 0xfbuy; 0xffuy; 0xfeuy |] + let result = base64.urlsafe_b64encode input + // URL-safe uses - and _ instead of + and / + result |> equal (builtins.bytes "-__-"B) + +[] +let ``test urlsafe_b64decode from string works`` () = + let encoded = "-__-" + let result = base64.urlsafe_b64decode encoded + result |> equal (builtins.bytes [| 0xfbuy; 0xffuy; 0xfeuy |]) + +[] +let ``test urlsafe_b64decode from bytes works`` () = + let encoded = builtins.bytes "-__-"B + let result = base64.urlsafe_b64decode encoded + result |> equal (builtins.bytes [| 0xfbuy; 0xffuy; 0xfeuy |]) + +[] +let ``test b32encode works`` () = + let input = builtins.bytes "Hello"B + let result = base64.b32encode input + result |> equal (builtins.bytes "JBSWY3DP"B) + +[] +let ``test b16encode works`` () = + let input = builtins.bytes "Hello"B + let result = base64.b16encode input + result |> equal (builtins.bytes "48656C6C6F"B) + +[] +let ``test empty input works`` () = + let input = builtins.bytes [||] + let encoded = base64.b64encode input + let decoded = base64.b64decode encoded + decoded |> equal (builtins.bytes [||]) diff --git a/test/TestLogging.fs b/test/TestLogging.fs new file mode 100644 index 0000000..8f95323 --- /dev/null +++ b/test/TestLogging.fs @@ -0,0 +1,97 @@ +module Fable.Python.Tests.Logging + +open Util.Testing +open Fable.Python.Logging + +[] +let ``test getLogger works`` () = + let logger = logging.getLogger "test" + logger.name |> equal "test" + +[] +let ``test getLogger without name returns root logger`` () = + let logger = logging.getLogger () + logger.name |> equal "root" + +[] +let ``test setLevel works`` () = + let logger = logging.getLogger "test_level" + logger.setLevel Level.DEBUG + logger.getEffectiveLevel () |> equal Level.DEBUG + +[] +let ``test isEnabledFor works`` () = + let logger = logging.getLogger "test_enabled" + logger.setLevel Level.WARNING + logger.isEnabledFor Level.ERROR |> equal true + logger.isEnabledFor Level.DEBUG |> equal false + +[] +let ``test debug logging works`` () = + let logger = logging.getLogger "test_debug" + logger.setLevel Level.DEBUG + // Just verify it doesn't throw + logger.debug "Debug message" + true |> equal true + +[] +let ``test info logging works`` () = + let logger = logging.getLogger "test_info" + logger.setLevel Level.INFO + logger.info "Info message" + true |> equal true + +[] +let ``test warning logging works`` () = + let logger = logging.getLogger "test_warning" + logger.warning "Warning message" + true |> equal true + +[] +let ``test error logging works`` () = + let logger = logging.getLogger "test_error" + logger.error "Error message" + true |> equal true + +[] +let ``test critical logging works`` () = + let logger = logging.getLogger "test_critical" + logger.critical "Critical message" + true |> equal true + +[] +let ``test log with level works`` () = + let logger = logging.getLogger "test_log" + logger.setLevel Level.DEBUG + logger.log (Level.INFO, "Log message with level") + true |> equal true + +[] +let ``test Level constants`` () = + Level.DEBUG |> equal 10 + Level.INFO |> equal 20 + Level.WARNING |> equal 30 + Level.ERROR |> equal 40 + Level.CRITICAL |> equal 50 + Level.NOTSET |> equal 0 + +[] +let ``test logging module level constants`` () = + logging.DEBUG |> equal 10 + logging.INFO |> equal 20 + logging.WARNING |> equal 30 + logging.ERROR |> equal 40 + logging.CRITICAL |> equal 50 + +[] +let ``test hasHandlers works`` () = + let logger = logging.getLogger "test_has_handlers" + // A new logger typically has no handlers + // (unless propagating to parent that has handlers) + let _ = logger.hasHandlers () + true |> equal true + +[] +let ``test basicConfig works`` () = + logging.basicConfig (level = Level.DEBUG, format = "%(message)s") + true |> equal true diff --git a/test/TestMath.fs b/test/TestMath.fs new file mode 100644 index 0000000..55349f8 --- /dev/null +++ b/test/TestMath.fs @@ -0,0 +1,121 @@ +module Fable.Python.Tests.Math + +open Util.Testing +open Fable.Python.Math + +[] +let ``test ceil works`` () = + math.ceil 2.3 |> equal 3 + math.ceil 2.9 |> equal 3 + math.ceil -1.5 |> equal -1 + +[] +let ``test floor works`` () = + math.floor 2.3 |> equal 2 + math.floor 2.9 |> equal 2 + math.floor -1.5 |> equal -2 + +[] +let ``test comb works`` () = + math.comb 5 2 |> equal 10 + math.comb 10 3 |> equal 120 + +[] +let ``test copysign works`` () = + math.copysign 1.0 -1 |> equal -1.0 + math.copysign -1.0 1 |> equal 1.0 + +[] +let ``test fabs works`` () = + math.fabs -5.0 |> equal 5.0 + math.fabs 5.0 |> equal 5.0 + +[] +let ``test factorial works`` () = + math.factorial 5.0 |> equal 120.0 + math.factorial 0.0 |> equal 1.0 + +[] +let ``test fmod works`` () = + math.fmod 10 3 |> equal 1 + math.fmod 7 2 |> equal 1 + +[] +let ``test gcd works`` () = + math.gcd (12, 8) |> equal 4 + math.gcd (15, 25) |> equal 5 + +[] +let ``test lcm works`` () = + math.lcm (4, 6) |> equal 12 + math.lcm (3, 5) |> equal 15 + +[] +let ``test isfinite works`` () = + math.isfinite 1.0 |> equal true + math.isfinite infinity |> equal false + math.isfinite nan |> equal false + +[] +let ``test isinf works`` () = + math.isinf infinity |> equal true + math.isinf (-infinity) |> equal true + math.isinf 1.0 |> equal false + +[] +let ``test isnan works`` () = + math.isnan nan |> equal true + math.isnan 1.0 |> equal false + +[] +let ``test exp works`` () = + math.exp 0.0 |> equal 1.0 + math.exp 1.0 |> fun x -> (x > 2.718 && x < 2.719) |> equal true + +[] +let ``test log works`` () = + math.log 1.0 |> equal 0.0 + math.log (math.exp 1.0) |> fun x -> (x > 0.999 && x < 1.001) |> equal true + +[] +let ``test log2 works`` () = + math.log2 8.0 |> equal 3.0 + math.log2 1.0 |> equal 0.0 + +[] +let ``test log10 works`` () = + math.log10 100.0 |> equal 2.0 + math.log10 1.0 |> equal 0.0 + +[] +let ``test pow works`` () = + math.pow 2.0 3.0 |> equal 8.0 + math.pow 10.0 2.0 |> equal 100.0 + +[] +let ``test sin works`` () = + math.sin 0.0 |> equal 0.0 + +[] +let ``test cos works`` () = + math.cos 0.0 |> equal 1.0 + +[] +let ``test tan works`` () = + math.tan 0.0 |> equal 0.0 + +[] +let ``test asin works`` () = + math.asin 0.0 |> equal 0.0 + +[] +let ``test acos works`` () = + math.acos 1.0 |> equal 0.0 + +[] +let ``test atan works`` () = + math.atan 0.0 |> equal 0.0 + +[] +let ``test atan2 works`` () = + math.atan2 0.0 1.0 |> equal 0.0 diff --git a/test/TestRandom.fs b/test/TestRandom.fs new file mode 100644 index 0000000..7dfbae0 --- /dev/null +++ b/test/TestRandom.fs @@ -0,0 +1,121 @@ +module Fable.Python.Tests.Random + +open Util.Testing +open Fable.Python.Random + +[] +let ``test seed with int works`` () = + random.seed 42 + let r1 = random.random () + random.seed 42 + let r2 = random.random () + r1 |> equal r2 + +[] +let ``test random returns value in range`` () = + random.seed 42 + let r = random.random () + (r >= 0.0 && r < 1.0) |> equal true + +[] +let ``test uniform works`` () = + random.seed 42 + let r = random.uniform (10.0, 20.0) + (r >= 10.0 && r <= 20.0) |> equal true + +[] +let ``test randint works`` () = + random.seed 42 + let r = random.randint (1, 10) + (r >= 1 && r <= 10) |> equal true + +[] +let ``test randrange with stop works`` () = + random.seed 42 + let r = random.randrange 10 + (r >= 0 && r < 10) |> equal true + +[] +let ``test randrange with start and stop works`` () = + random.seed 42 + let r = random.randrange (5, 10) + (r >= 5 && r < 10) |> equal true + +[] +let ``test randrange with step works`` () = + random.seed 42 + let r = random.randrange (0, 10, 2) + (r >= 0 && r < 10 && r % 2 = 0) |> equal true + +[] +let ``test choice with array works`` () = + random.seed 42 + let arr = [| 1; 2; 3; 4; 5 |] + let r = random.choice arr + (Array.contains r arr) |> equal true + +[] +let ``test choice with list works`` () = + random.seed 42 + let lst = [ "a"; "b"; "c" ] + let r = random.choice lst + (List.contains r lst) |> equal true + +[] +let ``test sample works`` () = + random.seed 42 + let arr = [| 1; 2; 3; 4; 5 |] + let r = random.sample (arr, 3) + Seq.length r |> equal 3 + +[] +let ``test choices works`` () = + random.seed 42 + let arr = [| 1; 2; 3 |] + let r = random.choices (arr, 5) + Seq.length r |> equal 5 + +[] +let ``test shuffle works`` () = + random.seed 42 + let arr = ResizeArray [ 1; 2; 3; 4; 5 ] + random.shuffle arr + // After shuffle, the array should still contain all original elements + (arr |> Seq.sort |> Seq.toList) |> equal [ 1; 2; 3; 4; 5 ] + +[] +let ``test getrandbits works`` () = + random.seed 42 + let r = random.getrandbits 8 + (r >= 0 && r < 256) |> equal true + +[] +let ``test gauss works`` () = + random.seed 42 + // Just verify it returns a float without error + let r = random.gauss (0.0, 1.0) + (r = r) |> equal true // NaN check + +[] +let ``test expovariate works`` () = + random.seed 42 + let r = random.expovariate 1.0 + (r >= 0.0) |> equal true + +[] +let ``test betavariate works`` () = + random.seed 42 + let r = random.betavariate (2.0, 5.0) + (r >= 0.0 && r <= 1.0) |> equal true + +[] +let ``test triangular works`` () = + random.seed 42 + let r = random.triangular (0.0, 10.0, 5.0) + (r >= 0.0 && r <= 10.0) |> equal true + +[] +let ``test normalvariate works`` () = + random.seed 42 + let r = random.normalvariate (0.0, 1.0) + (r = r) |> equal true // NaN check diff --git a/test/TestString.fs b/test/TestString.fs index c92b7c8..d449e77 100644 --- a/test/TestString.fs +++ b/test/TestString.fs @@ -12,3 +12,96 @@ let ``test string format works`` () = let ``test string format 2 works`` () = let result = "The sum of {0} + 2 is {1}".format (1, 1 + 2) result |> equal "The sum of 1 + 2 is 3" + +// String module constants + +[] +let ``test ascii_letters constant`` () = + pyString.ascii_letters |> equal "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +[] +let ``test ascii_lowercase constant`` () = + pyString.ascii_lowercase |> equal "abcdefghijklmnopqrstuvwxyz" + +[] +let ``test ascii_uppercase constant`` () = + pyString.ascii_uppercase |> equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +[] +let ``test digits constant`` () = + pyString.digits |> equal "0123456789" + +[] +let ``test hexdigits constant`` () = + pyString.hexdigits |> equal "0123456789abcdefABCDEF" + +[] +let ``test octdigits constant`` () = + pyString.octdigits |> equal "01234567" + +[] +let ``test punctuation constant`` () = + // Just verify it contains some expected punctuation + pyString.punctuation.Contains "!" |> equal true + pyString.punctuation.Contains "." |> equal true + pyString.punctuation.Contains "@" |> equal true + +[] +let ``test printable constant`` () = + // Printable includes digits, letters, punctuation, whitespace + pyString.printable.Contains "a" |> equal true + pyString.printable.Contains "0" |> equal true + pyString.printable.Contains " " |> equal true + +[] +let ``test whitespace constant`` () = + // Whitespace includes space, tab, newline, etc. + pyString.whitespace.Contains " " |> equal true + pyString.whitespace.Contains "\t" |> equal true + pyString.whitespace.Contains "\n" |> equal true + +// capwords function + +[] +let ``test capwords works`` () = + pyString.capwords "hello world" |> equal "Hello World" + +[] +let ``test capwords with multiple spaces`` () = + pyString.capwords "hello world" |> equal "Hello World" + +[] +let ``test capwords with custom separator`` () = + pyString.capwords ("hello-world", "-") |> equal "Hello-World" + +// Template class + +[] +let ``test Template substitute with dict works`` () = + let t = Template "$who likes $what" + let result = t.substitute {| who = "tim"; what = "kung pao" |} + result |> equal "tim likes kung pao" + +[] +let ``test Template safe_substitute works`` () = + let t = Template "$who likes $what" + let result = t.safe_substitute {| who = "tim" |} + result |> equal "tim likes $what" + +[] +let ``test Template template property`` () = + let t = Template "$name" + t.template |> equal "$name" + +[] +let ``test Template is_valid works`` () = + let t = Template "$valid" + t.is_valid () |> equal true + +[] +let ``test Template get_identifiers works`` () = + let t = Template "$who likes $what" + let ids = t.get_identifiers () + Seq.length ids |> equal 2 + Seq.contains "who" ids |> equal true + Seq.contains "what" ids |> equal true diff --git a/uv.lock b/uv.lock index dfa132e..fd4c328 100644 --- a/uv.lock +++ b/uv.lock @@ -161,7 +161,7 @@ wheels = [ [[package]] name = "fable-python" -version = "5.0.0a20" +version = "0.0.0" source = { virtual = "." } dependencies = [ { name = "fable-library" },