Compare commits
107 Commits
PSI-7.0.7.
...
PSI-7.0.7.
| Author | SHA1 | Date | |
|---|---|---|---|
| 43f6fd5060 | |||
|
|
c8d596034a | ||
|
|
1d3459982c | ||
|
|
9934242ccf | ||
|
|
e6dab90bf4 | ||
|
|
c680b9bebd | ||
|
|
9555ca05b8 | ||
|
|
0cd56fa40b | ||
|
|
485ac85fa5 | ||
| 1148931226 | |||
| 73f80cced9 | |||
| 152c72bab0 | |||
|
|
00dc55b8a2 | ||
|
|
524f81b8bd | ||
|
|
80e62031e9 | ||
|
|
d87fd0db01 | ||
|
|
6636b4b9e7 | ||
|
|
b51d1de283 | ||
|
|
38c99df2e0 | ||
|
|
688195a273 | ||
|
|
d691acc001 | ||
|
|
059d32a975 | ||
|
|
5c99031157 | ||
|
|
a01c671399 | ||
|
|
b2c80efd33 | ||
|
|
39b6fa26da | ||
|
|
3ee6097ab7 | ||
|
|
500a57738b | ||
|
|
f488765631 | ||
|
|
3a2d225682 | ||
|
|
1d056c6fe4 | ||
| 0f086126b7 | |||
| 5b058d7d27 | |||
|
|
42604fc794 | ||
|
|
4ecc0daa79 | ||
|
|
5a1f3ecc8b | ||
|
|
cb97d662a7 | ||
|
|
d4fab0d20e | ||
|
|
0c13e6ba6c | ||
|
|
8f1243da40 | ||
|
|
fe9995c0b5 | ||
|
|
f56412d6a5 | ||
|
|
07d18c55ba | ||
|
|
9f97f25669 | ||
| 912a82c0b5 | |||
| acf2241fd0 | |||
|
|
b878295d06 | ||
| f41f11c7f6 | |||
|
|
216359974c | ||
|
|
17ad04505e | ||
|
|
3500a02034 | ||
|
|
52b18d56a0 | ||
|
|
5507646ce7 | ||
|
|
625c2ef159 | ||
|
|
bdaca51d96 | ||
|
|
9655b78e11 | ||
|
|
e11f88017d | ||
|
|
8a3020033e | ||
|
|
bd1af9ac95 | ||
|
|
d5959ca20a | ||
|
|
e10dcede7d | ||
|
|
c042b08ab0 | ||
| 28d33d43ec | |||
| b72fcff44d | |||
| d3dc2afd8d | |||
|
|
5eff3803a8 | ||
|
|
151256533f | ||
|
|
3b484f58d3 | ||
|
|
d3f93746a8 | ||
|
|
f99a1cb0f3 | ||
|
|
531a769007 | ||
|
|
3e51491628 | ||
| 0b01fb20db | |||
|
|
c7a769e5da | ||
|
|
a9fd57a865 | ||
| e862f0e95f | |||
| 172597e0e6 | |||
| bcdeeed206 | |||
|
|
b6626e4f60 | ||
|
|
e1c1bb8b1b | ||
|
|
90ae51e8f2 | ||
|
|
832abbd3b1 | ||
|
|
52cc68433f | ||
|
|
f430389ee7 | ||
|
|
bded79f14d | ||
|
|
2ff44cb386 | ||
|
|
a9ade9669a | ||
|
|
f902d70006 | ||
|
|
e22d74310b | ||
|
|
e5ad12e638 | ||
|
|
b963a4564e | ||
|
|
579c125b01 | ||
|
|
84d9617375 | ||
|
|
d66e90a016 | ||
|
|
373e5440ac | ||
|
|
dec23501e1 | ||
|
|
11a4bed9aa | ||
|
|
bf4a4c6b78 | ||
|
|
b54d4b9a24 | ||
|
|
84f4771691 | ||
|
|
1dc34a02e2 | ||
|
|
3ab22818da | ||
|
|
1ab474638d | ||
|
|
eea361bf5e | ||
|
|
d9052f7105 | ||
|
|
fa00572780 | ||
|
|
636f5517b2 |
@@ -25,22 +25,6 @@ init:
|
||||
# Set clone depth (do not fetch complete history)
|
||||
clone_depth: 5
|
||||
|
||||
# Skip commits affecting only specific files
|
||||
skip_commits:
|
||||
files:
|
||||
# Removed - 'documentation/*'
|
||||
# Unfortunately Appveyor only looks at the HEAD (latest) commit, so if
|
||||
# the last commit pushed to a PR only updates the Release Notes the whole
|
||||
# PR build gets skipped. GitHub Actions looks at the complete list
|
||||
# of files changed since its last build, which is much more useful.
|
||||
- 'startup/*'
|
||||
- '.github/*'
|
||||
- '.tools/*'
|
||||
- '.gitattributes'
|
||||
- '.lgtm.yml'
|
||||
- '**/*.html'
|
||||
- '**/*.md'
|
||||
|
||||
#---------------------------------#
|
||||
# build matrix configuration #
|
||||
#---------------------------------#
|
||||
|
||||
@@ -32,22 +32,6 @@ init:
|
||||
# Set clone depth (do not fetch complete history)
|
||||
clone_depth: 5
|
||||
|
||||
# Skip commits affecting only specific files
|
||||
skip_commits:
|
||||
files:
|
||||
# Removed - 'documentation/*'
|
||||
# Unfortunately Appveyor only looks at the HEAD (latest) commit, so if
|
||||
# the last commit pushed to a PR only updates the Release Notes the whole
|
||||
# PR build gets skipped. GitHub Actions looks at the complete list
|
||||
# of files changed since its last build, which is much more useful.
|
||||
- 'startup/*'
|
||||
- '.github/*'
|
||||
- '.tools/*'
|
||||
- '.lgtm.yml'
|
||||
- '.gitattributes'
|
||||
- '**/*.html'
|
||||
- '**/*.md'
|
||||
|
||||
#---------------------------------#
|
||||
# build matrix configuration #
|
||||
#---------------------------------#
|
||||
|
||||
2
.ci
2
.ci
Submodule .ci updated: aea7906839...1e0e326f74
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
||||
# Documentation for this file: https://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
# Unix-style newlines ending every file,
|
||||
# as some compilers complain about files not ending in newline
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
13
.github/workflows/check-editorconfig.yml
vendored
Normal file
13
.github/workflows/check-editorconfig.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Check EditorConfig
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
editorconfig:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: EditorConfig-Action
|
||||
uses: greut/eclint-action@v0
|
||||
75
.github/workflows/ci-scripts-build.yml
vendored
75
.github/workflows/ci-scripts-build.yml
vendored
@@ -43,11 +43,8 @@ jobs:
|
||||
env:
|
||||
CMP: ${{ matrix.cmp }}
|
||||
BCFG: ${{ matrix.configuration }}
|
||||
WINE: ${{ matrix.wine }}
|
||||
RTEMS: ${{ matrix.rtems }}
|
||||
RTEMS_TARGET: ${{ matrix.rtems_target }}
|
||||
CI_CROSS_TARGETS: ${{ matrix.cross }}
|
||||
EXTRA: ${{ matrix.extra }}
|
||||
EXTRA1: ${{ matrix.extra1 }}
|
||||
TEST: ${{ matrix.test }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -57,13 +54,13 @@ jobs:
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
wine: "64"
|
||||
cross: "windows-x64-mingw"
|
||||
name: "Ub-20 gcc-9 + MinGW"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: static
|
||||
wine: "64"
|
||||
cross: "windows-x64-mingw"
|
||||
name: "Ub-20 gcc-9 + MinGW, static"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
@@ -75,8 +72,7 @@ jobs:
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: static
|
||||
extra: "CMD_CFLAGS=-funsigned-char"
|
||||
extra1: "CMD_CXXFLAGS=-funsigned-char"
|
||||
extra: "CMD_CFLAGS=-funsigned-char CMD_CXXFLAGS=-funsigned-char"
|
||||
name: "Ub-20 gcc-9 unsigned char"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
@@ -93,67 +89,42 @@ jobs:
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "5"
|
||||
rtems_target: RTEMS-pc686-qemu
|
||||
cross: "RTEMS-pc686-qemu@5"
|
||||
name: "Ub-20 gcc-9 + RT-5.1 pc686"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "5"
|
||||
rtems_target: RTEMS-beatnik
|
||||
cross: "RTEMS-beatnik@5"
|
||||
test: NO
|
||||
name: "Ub-20 gcc-9 + RT-5.1 beatnik"
|
||||
|
||||
# Only build one RTEMS target per CPU family
|
||||
# unless it's running the tests
|
||||
#
|
||||
# - os: ubuntu-20.04
|
||||
# cmp: gcc
|
||||
# configuration: default
|
||||
# rtems: "5"
|
||||
# rtems_target: RTEMS-mvme3100
|
||||
# test: NO
|
||||
# name: "Ub-20 gcc-9 + RT-5.1 mvme3100"
|
||||
#
|
||||
# - os: ubuntu-20.04
|
||||
# cmp: gcc
|
||||
# configuration: default
|
||||
# rtems: "5"
|
||||
# rtems_target: RTEMS-qoriq_e500
|
||||
# test: NO
|
||||
# name: "Ub-20 gcc-9 + RT-5.1 qoriq_e500"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "5"
|
||||
rtems_target: RTEMS-xilinx_zynq_a9_qemu
|
||||
cross: "RTEMS-xilinx_zynq_a9_qemu@5"
|
||||
test: NO
|
||||
name: "Ub-20 gcc-9 + RT-5.1 xilinx_zynq_a9_qemu"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "5"
|
||||
rtems_target: RTEMS-uC5282
|
||||
cross: "RTEMS-uC5282@5"
|
||||
test: NO
|
||||
name: "Ub-20 gcc-9 + RT-5.1 uC5282"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "4.10"
|
||||
name: "Ub-20 gcc-9 + RT-4.10"
|
||||
rtems_target: RTEMS-pc386-qemu
|
||||
cross: "RTEMS-pc386-qemu@4.10"
|
||||
test: NO
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
rtems: "4.9"
|
||||
name: "Ub-20 gcc-9 + RT-4.9"
|
||||
rtems_target: RTEMS-pc386-qemu
|
||||
cross: "RTEMS-pc386-qemu@4.9"
|
||||
|
||||
- os: macos-latest
|
||||
cmp: clang
|
||||
@@ -162,13 +133,15 @@ jobs:
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
configuration: default
|
||||
configuration: debug
|
||||
name: "Win2019 MSC-19"
|
||||
extra: "CMD_CXXFLAGS=-analysis"
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
configuration: static
|
||||
configuration: static-debug
|
||||
name: "Win2019 MSC-19, static"
|
||||
extra: "CMD_CXXFLAGS=-analysis"
|
||||
|
||||
- os: windows-2019
|
||||
cmp: vs2019
|
||||
@@ -180,6 +153,26 @@ jobs:
|
||||
configuration: default
|
||||
name: "Win2019 mingw"
|
||||
|
||||
# Cross builds
|
||||
|
||||
- os: ubuntu-latest
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
name: "Cross linux-aarch64"
|
||||
cross: linux-aarch64
|
||||
|
||||
- os: ubuntu-latest
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
name: "Cross linux-arm gnueabi"
|
||||
cross: linux-arm@arm-linux-gnueabi
|
||||
|
||||
- os: ubuntu-latest
|
||||
cmp: gcc
|
||||
configuration: default
|
||||
name: "Cross linux-arm gnueabihf"
|
||||
cross: linux-arm@arm-linux-gnueabihf
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@
|
||||
/modules/Makefile.local
|
||||
O.*/
|
||||
/QtC-*
|
||||
/.qtc_*
|
||||
/.vscode/
|
||||
*.orig
|
||||
*.log
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,7 @@
|
||||
[submodule "modules/pvData"]
|
||||
path = modules/pvData
|
||||
url = https://github.com/epics-base/pvDataCPP
|
||||
# url = https://github.com/epics-base/pvDataCPP
|
||||
url = git@git.psi.ch:epics_base/pvData.git
|
||||
branch = master
|
||||
[submodule "modules/pvAccess"]
|
||||
path = modules/pvAccess
|
||||
|
||||
@@ -20,9 +20,9 @@ maybedie() {
|
||||
|
||||
usage() {
|
||||
cat <<EOF >&2
|
||||
usage: $0 [-v] [-s] <rev> [<outfile> [<prefix>]]
|
||||
usage: $0 [-v] [-d] <rev> [<outfile> [<prefix>]]
|
||||
|
||||
<rev> may be any git revision spec. (tag, branch, or commit id).
|
||||
<rev> may be any git revision spec. (tag or, using -d, branch or commit id).
|
||||
|
||||
If provided, <outfile> must end with ".tar", ".tar.gz" or ".tar.bz2".
|
||||
If <outfile> is omitted, "base-<rev>.tar.gz" will be used.
|
||||
|
||||
@@ -83,8 +83,9 @@ IOCS_APPL_TOP = $(INSTALL_ABSOLUTE)
|
||||
|
||||
#-------------------------------------------------------
|
||||
# How to portably check the flags to make
|
||||
makeflags := $(firstword $(filter-out -,$(filter-out --%,$(MAKEFLAGS))))
|
||||
define checkflags
|
||||
make-$1 = $(findstring $1,$(filter-out --%,$(MAKEFLAGS)))
|
||||
make-$1 := $(findstring $1,$(makeflags))
|
||||
endef
|
||||
# This is extensible to most single letter flags:
|
||||
$(foreach flag,s q, $(eval $(call checkflags,$(flag))))
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
|
||||
# Include definitions common to all Unix targets
|
||||
include $(CONFIG)/os/CONFIG.Common.UnixCommon
|
||||
GNU = NO
|
||||
CMPLR_CLASS = clang
|
||||
CC = clang
|
||||
CCC = clang++
|
||||
|
||||
OS_CLASS = freebsd
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
|
||||
#Include definitions common to unix hosts
|
||||
include $(CONFIG)/os/CONFIG.UnixCommon.Common
|
||||
CMPLR_CLASS = clang
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Definitions for freebsd-x86 host - freebsd-x86 target builds
|
||||
# Sites may override these definitions in CONFIG_SITE.freebsd-x86.freebsd-x86
|
||||
#-------------------------------------------------------
|
||||
GNU_DIR=/usr/local
|
||||
|
||||
# Include common gnu compiler definitions
|
||||
include $(CONFIG)/CONFIG.gnuCommon
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Definitions for freebsd-x86_64 host - freebsd-x86_64 target builds
|
||||
# Sites may override these definitions in CONFIG_SITE.freebsd-x86_64.freebsd-x86_64
|
||||
#-------------------------------------------------------
|
||||
GNU_DIR=/usr/local
|
||||
|
||||
# Include common gnu compiler definitions
|
||||
include $(CONFIG)/CONFIG.gnuCommon
|
||||
|
||||
@@ -12,6 +12,9 @@ ifneq (,$(wildcard /opt/homebrew))
|
||||
else ifneq (,$(wildcard /usr/local/Homebrew))
|
||||
# Default location on x86_64
|
||||
HOMEBREW_DIR = /usr/local
|
||||
else ifneq (,$(wildcard /opt/local/include/readline))
|
||||
# MacPorts
|
||||
READLINE_DIR = /opt/local
|
||||
endif
|
||||
|
||||
# Look for Homebrew's readline
|
||||
|
||||
@@ -45,5 +45,5 @@ COMMANDLINE_LIBRARY ?= READLINE
|
||||
COMMANDLINE_LIBRARY ?= EPICS
|
||||
# endif
|
||||
#else
|
||||
COMMANDLINE_LIBRARY ?= EPICS
|
||||
COMMANDLINE_LIBRARY ?= $(strip $(if $(wildcard $(if $(GNU_DIR),$(GNU_DIR)/include/readline/readline.h)), READLINE, EPICS))
|
||||
#endif
|
||||
|
||||
@@ -9,13 +9,102 @@ important to read more than just the first section to understand everything that
|
||||
has changed in each release.
|
||||
|
||||
The PVA submodules each have their own individual sets of release notes which
|
||||
should also be read to understand what has changed since earlier releases.
|
||||
should also be read to understand what has changed since earlier releases:
|
||||
|
||||
- [normativeTypes](https://github.com/epics-base/normativeTypesCPP/blob/master/documentation/RELEASE_NOTES.md)
|
||||
- [pvAccess](http://epics-base.github.io/pvAccessCPP/pvarelease_notes.html)
|
||||
- [pvData](http://epics-base.github.io/pvDataCPP/release_notes.html)
|
||||
- [pvDatabase](https://github.com/epics-base/pvDatabaseCPP/blob/master/documentation/RELEASE_NOTES.md)
|
||||
- [pva2pva](https://epics-base.github.io/pva2pva/release_notes.html)
|
||||
- [pvaClient](https://github.com/epics-base/pvaClientCPP/blob/master/documentation/RELEASE_NOTES.md)
|
||||
|
||||
**This version of EPICS has not been released yet.**
|
||||
|
||||
## Changes made on the 7.0 branch since 7.0.7
|
||||
|
||||
<!-- Insert new items immediately below here ... -->
|
||||
### Add `$EPICS_CLI_TIMEOUT`
|
||||
|
||||
Add support for CA tools timeout from environment variable `$EPICS_CLI_TIMEOUT`
|
||||
which sets the default the default timeout for `caget` et al.
|
||||
The `-w` argument continues to take precedence.
|
||||
|
||||
### Fixed leak from a non-EPICS thread on WIN32
|
||||
|
||||
On Windows targets, if a thread not created by `epicsThreadCreate*()` directly
|
||||
or indirectly calls an `epicsThread*()` function, a specific tracking struct
|
||||
is allocated. Prior to this release the allocation would not be `free()`d,
|
||||
resulting in a memory leak.
|
||||
|
||||
A similar issue on POSIX targets was previously fixed.
|
||||
|
||||
### Change compiler for FreeBSD to clang
|
||||
|
||||
The default compiler for FreeBSD targets changes from GCC to clang.
|
||||
|
||||
### Expose `dbCreateAlias` in IOC shell
|
||||
|
||||
Add a new IOC shell command `dbCreateAlias` allow record aliases to be added.
|
||||
Intended for use before `iocInit`. eg. to add an alias "bar" for a record "foo".
|
||||
|
||||
```
|
||||
dbLoadRecords("some.db") # includes: record(ai, "foo") { ...
|
||||
dbCreateAlias("foo", "bar")
|
||||
iocInit()
|
||||
```
|
||||
|
||||
### dbEvent eventsRemaining missed on cancel
|
||||
|
||||
In some cases, RSRV may queue a subscription update, but not flush it.
|
||||
This partially addresses this issue.
|
||||
|
||||
### subRecord on bad INP links
|
||||
|
||||
Previously, if a subRecord has an invalid `INP*` link, it was silently failing
|
||||
(and not running the proc function). Now the the status code returned by the
|
||||
subroutine is returned from `dbProcess()`.
|
||||
|
||||
### COMMANDLINE_LIBRARY fallback to GNU_DIR
|
||||
|
||||
Fall back to the previous behavior when searching for `readline.h` with older compilers.
|
||||
|
||||
### Search for readline installed via HomeBrew.
|
||||
|
||||
Look for `/opt/local/include/readline` on OSX.
|
||||
|
||||
### Always stop worker threads
|
||||
|
||||
The SCAN and callback threads are now stopped during normal IOC shutdown.
|
||||
|
||||
### Allow runtime bypass of free list allocator
|
||||
|
||||
The environment variable `$EPICS_FREELIST_BYPASS` may be set to `YES` to cause the `freeListLib` functions to always call directly to `malloc()`/`free()`. May be useful when troubleshooting some kinds of memory allocation bugs which would otherwise be "hidden". eg. use-after-free data races. This may also improve the results of dynamic analysis tools which are not aware of this internal free list.
|
||||
|
||||
### `compress` record enhancement
|
||||
|
||||
The compress record now supports the use of partially-filled buffers when using
|
||||
any of the N-to-one algorithms. This is achieved by setting the new field `PBUF`
|
||||
to `YES`.
|
||||
|
||||
### Extended timestamp channel filter
|
||||
|
||||
The `"ts"` filter can now retrieve the record's timestamp in several numeric
|
||||
and string formats, some of which support full nanosecond precision.
|
||||
|
||||
Hal$ caget -a test:channel
|
||||
test:channel 2021-03-11 18:23:48.265386 42
|
||||
Hal$ caget -f9 'test:channel.{"ts": {"num": "dbl"}}'
|
||||
test:channel.{"ts": {"num": "dbl"}} 984331428.265386105
|
||||
Hal$ caget 'test:channel.{"ts": {"str": "iso"}}'
|
||||
test:channel.{"ts": {"str": "iso"}} 2021-03-11T18:23:48.265386+0100
|
||||
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts"}}'
|
||||
test:channel.{"ts": {"num": "ts"}} 2 984331428.0 265386163.0
|
||||
|
||||
More information is included in the filters documentation, which can be found in
|
||||
the `html/filters.html` document that is generated during the build
|
||||
|
||||
### Allow adding new error symbols at any time
|
||||
|
||||
`errSymbolAdd` can now be called after early initialization.
|
||||
|
||||
### Add conditional output (OOPT) to the longout record
|
||||
|
||||
@@ -196,6 +285,32 @@ This release fixed the leak on POSIX targets.
|
||||
See the associated github [issue 241](https://github.com/epics-base/epics-base/issues/241)
|
||||
for WIN32 status.
|
||||
|
||||
### Fixed leak from a non-EPICS thread
|
||||
|
||||
On some targets, if a thread not created by `epicsThreadCreate*()` directly
|
||||
or indirectly calls an `epicsThread*()` function, a specific tracking struct
|
||||
is allocated.
|
||||
|
||||
Prior to this release, on POSIX and WIN32 targets, this
|
||||
allocation would not be `free()`d, resulting in a memory leak.
|
||||
|
||||
This release fixed the leak on POSIX and WIN32 targets (excluding
|
||||
MSVC before vs2012, and the WINE runtime).
|
||||
|
||||
### Fixed leak from a non-EPICS thread
|
||||
|
||||
On some targets, if a thread not created by `epicsThreadCreate*()` directly
|
||||
or indirectly calls an `epicsThread*()` function, a specific tracking struct
|
||||
is allocated.
|
||||
|
||||
Prior to this release, on POSIX and WIN32 targets, this
|
||||
struct would not be `free()`d, resulting in a memory leak.
|
||||
|
||||
This release fixed the leak on POSIX targets.
|
||||
|
||||
See the associated github [issue 241](https://github.com/epics-base/epics-base/issues/241)
|
||||
for WIN32 status.
|
||||
|
||||
### Fix `CHECK_RELEASE = WARN`
|
||||
|
||||
This now works again, it was broken in 2019 (7.0.3.1) by an errant commit.
|
||||
|
||||
@@ -450,7 +450,7 @@ void timeIt ( tf *pfunc, ti *pItems, unsigned iterations,
|
||||
epicsTimeStamp end_time;
|
||||
epicsTimeStamp start_time;
|
||||
double delay;
|
||||
unsigned inlineIter;
|
||||
unsigned inlineIter = 0;
|
||||
|
||||
epicsTimeGetCurrent ( &start_time );
|
||||
(*pfunc) ( pItems, iterations, &inlineIter );
|
||||
|
||||
@@ -56,6 +56,7 @@ extern "C" void epicsStdCall ca_dump_dbr (
|
||||
|
||||
if ( INVALID_DB_REQ ( type ) ) {
|
||||
printf ( "bad DBR type %ld\n", type );
|
||||
return;
|
||||
}
|
||||
|
||||
printf ( "%s\t", dbr_text[type] );
|
||||
|
||||
@@ -392,6 +392,8 @@ int main (int argc, char *argv[])
|
||||
|
||||
LINE_BUFFER(stdout); /* Configure stdout buffering */
|
||||
|
||||
use_ca_timeout_env ( &caTimeout);
|
||||
|
||||
while ((opt = getopt(argc, argv, ":taicnhsSVe:f:g:l:#:d:0:w:p:F:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'h': /* Print usage */
|
||||
@@ -437,8 +439,8 @@ int main (int argc, char *argv[])
|
||||
if(epicsScanDouble(optarg, &caTimeout) != 1)
|
||||
{
|
||||
fprintf(stderr, "'%s' is not a valid timeout value "
|
||||
"- ignored. ('caget -h' for help.)\n", optarg);
|
||||
caTimeout = DEFAULT_TIMEOUT;
|
||||
"- ignored, using '%.1f'. ('caget -h' for help.)\n",
|
||||
optarg, caTimeout);
|
||||
}
|
||||
break;
|
||||
case '#': /* Array count */
|
||||
|
||||
@@ -141,6 +141,8 @@ int main (int argc, char *argv[])
|
||||
|
||||
LINE_BUFFER(stdout); /* Configure stdout buffering */
|
||||
|
||||
use_ca_timeout_env ( &caTimeout);
|
||||
|
||||
while ((opt = getopt(argc, argv, ":nhVw:s:p:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'h': /* Print usage */
|
||||
@@ -150,11 +152,16 @@ int main (int argc, char *argv[])
|
||||
printf( "\nEPICS Version %s, CA Protocol version %s\n", EPICS_VERSION_STRING, ca_version() );
|
||||
return 0;
|
||||
case 'w': /* Set CA timeout value */
|
||||
/*
|
||||
* epicsScanDouble is a macro defined as epicsParseDouble,
|
||||
* (found in modules/libcom/src/misc) which will only
|
||||
* change caTimeout here if it finds an acceptable value.
|
||||
*/
|
||||
if(epicsScanDouble(optarg, &caTimeout) != 1)
|
||||
{
|
||||
fprintf(stderr, "'%s' is not a valid timeout value "
|
||||
"- ignored. ('cainfo -h' for help.)\n", optarg);
|
||||
caTimeout = DEFAULT_TIMEOUT;
|
||||
"- ignored, using '%.1f'. ('cainfo -h' for help.)\n",
|
||||
optarg, caTimeout);
|
||||
}
|
||||
break;
|
||||
case 's': /* ca_client_status interest level */
|
||||
|
||||
@@ -219,6 +219,8 @@ int main (int argc, char *argv[])
|
||||
|
||||
LINE_BUFFER(stdout); /* Configure stdout buffering */
|
||||
|
||||
use_ca_timeout_env ( &caTimeout);
|
||||
|
||||
while ((opt = getopt(argc, argv, ":nhVm:sSe:f:g:l:#:0:w:t:p:F:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'h': /* Print usage */
|
||||
@@ -251,11 +253,16 @@ int main (int argc, char *argv[])
|
||||
}
|
||||
break;
|
||||
case 'w': /* Set CA timeout value */
|
||||
/*
|
||||
* epicsScanDouble is a macro defined as epicsParseDouble,
|
||||
* (found in modules/libcom/src/misc) which will only
|
||||
* change caTimeout here if it finds an acceptable value.
|
||||
*/
|
||||
if(epicsScanDouble(optarg, &caTimeout) != 1)
|
||||
{
|
||||
fprintf(stderr, "'%s' is not a valid timeout value "
|
||||
"- ignored. ('camonitor -h' for help.)\n", optarg);
|
||||
caTimeout = DEFAULT_TIMEOUT;
|
||||
"- ignored, using '%.1f'. ('camonitor -h' for help.)\n",
|
||||
optarg, caTimeout);
|
||||
}
|
||||
break;
|
||||
case '#': /* Array count */
|
||||
|
||||
@@ -284,6 +284,8 @@ int main (int argc, char *argv[])
|
||||
LINE_BUFFER(stdout); /* Configure stdout buffering */
|
||||
putenv("POSIXLY_CORRECT="); /* Behave correct on GNU getopt systems */
|
||||
|
||||
use_ca_timeout_env ( &caTimeout);
|
||||
|
||||
while ((opt = getopt(argc, argv, ":cnlhatsVS#:w:p:F:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'h': /* Print usage */
|
||||
@@ -318,11 +320,16 @@ int main (int argc, char *argv[])
|
||||
request = callback;
|
||||
break;
|
||||
case 'w': /* Set CA timeout value */
|
||||
/*
|
||||
* epicsScanDouble is a macro defined as epicsParseDouble,
|
||||
* (found in modules/libcom/src/misc) which will only
|
||||
* change caTimeout here if it finds an acceptable value.
|
||||
*/
|
||||
if(epicsScanDouble(optarg, &caTimeout) != 1)
|
||||
{
|
||||
fprintf(stderr, "'%s' is not a valid timeout value "
|
||||
"- ignored. ('caput -h' for help.)\n", optarg);
|
||||
caTimeout = DEFAULT_TIMEOUT;
|
||||
"- ignored, using '%.1f'. ('caput -h' for help.)\n",
|
||||
optarg, caTimeout);
|
||||
}
|
||||
break;
|
||||
case '#': /* Array count */
|
||||
@@ -337,7 +344,7 @@ int main (int argc, char *argv[])
|
||||
if (sscanf(optarg,"%u", &caPriority) != 1)
|
||||
{
|
||||
fprintf(stderr, "'%s' is not a valid CA priority "
|
||||
"- ignored. ('caget -h' for help.)\n", optarg);
|
||||
"- ignored. ('caput -h' for help.)\n", optarg);
|
||||
caPriority = DEFAULT_CA_PRIORITY;
|
||||
}
|
||||
if (caPriority > CA_PRIORITY_MAX) caPriority = CA_PRIORITY_MAX;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include <alarm.h>
|
||||
#include <epicsTime.h>
|
||||
#include <epicsStdlib.h>
|
||||
#include <epicsString.h>
|
||||
#include <cadef.h>
|
||||
|
||||
@@ -53,7 +54,7 @@ char fieldSeparator = ' '; /* OFS default is whitespace */
|
||||
|
||||
int enumAsNr = 0; /* used for -n option - get DBF_ENUM as number */
|
||||
int charArrAsStr = 0; /* used for -S option - treat char array as (long) string */
|
||||
double caTimeout = 1.0; /* wait time default (see -w option) */
|
||||
double caTimeout = DEFAULT_TIMEOUT; /* wait time default (see -w option) */
|
||||
capri caPriority = DEFAULT_CA_PRIORITY; /* CA Priority */
|
||||
|
||||
#define TIMETEXTLEN 28 /* Length of timestamp text buffer */
|
||||
@@ -639,3 +640,22 @@ int connect_pvs (pv* pvs, int nPvs)
|
||||
}
|
||||
return returncode;
|
||||
}
|
||||
|
||||
|
||||
/* Set the timeout to EPICS_CLI_TIMEOUT */
|
||||
void use_ca_timeout_env ( double* timeout)
|
||||
{
|
||||
const char* tmoStr; /* contents of environment var */
|
||||
|
||||
if ((tmoStr = getenv("EPICS_CLI_TIMEOUT")) != NULL && timeout != NULL)
|
||||
{
|
||||
if(epicsScanDouble(tmoStr, timeout) != 1)
|
||||
{
|
||||
fprintf(stderr, "'%s' is not a valid timeout value "
|
||||
"(from 'EPICS_CLI_TIMEOUT' in the environment) - "
|
||||
"ignored. (use '-h' for help.)\n", tmoStr);
|
||||
*timeout = DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ extern char *dbr2str (const void *value, unsigned type);
|
||||
extern void print_time_val_sts (pv *pv, unsigned long reqElems);
|
||||
extern int create_pvs (pv *pvs, int nPvs, caCh *pCB );
|
||||
extern int connect_pvs (pv *pvs, int nPvs );
|
||||
extern void use_ca_timeout_env (double* timeout);
|
||||
|
||||
/*
|
||||
* no additions below this endif
|
||||
|
||||
@@ -58,6 +58,7 @@ typedef struct cbQueueSet {
|
||||
int shutdown; // use atomic
|
||||
int threadsConfigured;
|
||||
int threadsRunning;
|
||||
epicsThreadId *threads;
|
||||
} cbQueueSet;
|
||||
|
||||
static cbQueueSet callbackQueue[NUM_CALLBACK_PRIORITIES];
|
||||
@@ -242,11 +243,15 @@ void callbackStop(void)
|
||||
|
||||
for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) {
|
||||
cbQueueSet *mySet = &callbackQueue[i];
|
||||
int j;
|
||||
|
||||
while (epicsAtomicGetIntT(&mySet->threadsRunning)) {
|
||||
epicsEventSignal(mySet->semWakeUp);
|
||||
epicsEventWaitWithTimeout(startStopEvent, 0.1);
|
||||
}
|
||||
for(j=0; j<mySet->threadsConfigured; j++) {
|
||||
epicsThreadMustJoin(mySet->threads[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +271,8 @@ void callbackCleanup(void)
|
||||
mySet->semWakeUp = NULL;
|
||||
epicsRingPointerDelete(mySet->queue);
|
||||
mySet->queue = NULL;
|
||||
free(mySet->threads);
|
||||
mySet->threads = NULL;
|
||||
}
|
||||
|
||||
epicsTimerQueueRelease(timerQueue);
|
||||
@@ -297,17 +304,25 @@ void callbackInit(void)
|
||||
cantProceed("epicsRingPointerLockedCreate failed for %s\n",
|
||||
threadNamePrefix[i]);
|
||||
callbackQueue[i].queueOverflow = FALSE;
|
||||
|
||||
if (callbackQueue[i].threadsConfigured == 0)
|
||||
callbackQueue[i].threadsConfigured = callbackThreadsDefault;
|
||||
|
||||
callbackQueue[i].threads = callocMustSucceed(callbackQueue[i].threadsConfigured,
|
||||
sizeof(*callbackQueue[i].threads),
|
||||
"callbackInit");
|
||||
|
||||
for (j = 0; j < callbackQueue[i].threadsConfigured; j++) {
|
||||
epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
|
||||
opts.joinable = 1;
|
||||
opts.priority = threadPriority[i];
|
||||
opts.stackSize = epicsThreadStackBig;
|
||||
if (callbackQueue[i].threadsConfigured > 1 )
|
||||
sprintf(threadName, "%s-%d", threadNamePrefix[i], j);
|
||||
else
|
||||
strcpy(threadName, threadNamePrefix[i]);
|
||||
tid = epicsThreadCreate(threadName, threadPriority[i],
|
||||
epicsThreadGetStackSize(epicsThreadStackBig),
|
||||
(EPICSTHREADFUNC)callbackTask, &priorityValue[i]);
|
||||
callbackQueue[i].threads[j] = tid = epicsThreadCreateOpt(threadName,
|
||||
(EPICSTHREADFUNC)callbackTask, &priorityValue[i], &opts);
|
||||
if (tid == 0) {
|
||||
cantProceed("Failed to spawn callback thread %s\n", threadName);
|
||||
} else {
|
||||
|
||||
@@ -73,7 +73,7 @@ epicsExportAddress(int, dbAccessDebugPUTF);
|
||||
|
||||
DB_LOAD_RECORDS_HOOK_ROUTINE dbLoadRecordsHook = NULL;
|
||||
|
||||
static short mapDBFToDBR[DBF_NTYPES] = {
|
||||
static const short mapDBFToDBR[DBF_NTYPES] = {
|
||||
/* DBF_STRING => */ DBR_STRING,
|
||||
/* DBF_CHAR => */ DBR_CHAR,
|
||||
/* DBF_UCHAR => */ DBR_UCHAR,
|
||||
|
||||
@@ -575,15 +575,10 @@ long dbChannelOpen(dbChannel *chan)
|
||||
}
|
||||
|
||||
/* Set up type probe */
|
||||
probe.type = dbfl_type_val;
|
||||
probe.ctx = dbfl_context_read;
|
||||
memset(&probe, 0, sizeof(probe));
|
||||
probe.field_type = dbChannelExportType(chan);
|
||||
probe.no_elements = dbChannelElements(chan);
|
||||
probe.field_size = dbChannelFieldSize(chan);
|
||||
probe.sevr = NO_ALARM;
|
||||
probe.stat = NO_ALARM;
|
||||
probe.time.secPastEpoch = 0;
|
||||
probe.time.nsec = 0;
|
||||
|
||||
p = probe;
|
||||
|
||||
|
||||
@@ -91,20 +91,25 @@ For example these rates are all valid:
|
||||
1 second
|
||||
2 Hertz
|
||||
|
||||
The B<PINI> field specifies record processing at initialization. If it is set
|
||||
to YES during database configuration, the record is processed once at IOC
|
||||
initialization (before the normal scan tasks are started).
|
||||
The B<PINI> field specifies record processing at initialization. It can have the
|
||||
values NO, YES, RUN, RUNNING, PAUSE, and PAUSED. If it is set to YES during
|
||||
database configuration, the record is processed once at IOC initialization
|
||||
(before the normal scan tasks are started). For the other values see
|
||||
L<menuPini.dbd|menuPini> for more details.
|
||||
|
||||
The B<PHAS> field orders the records within a specific SCAN group. This is not
|
||||
meaningful for passive records. All records of a specified phase are processed
|
||||
before those with higher phase number. It is generally better practice to use
|
||||
linked passive records to enforce the order of processing rather than a phase
|
||||
number.
|
||||
The B<PHAS> field orders the records processed within a specific SCAN group or
|
||||
PINI processing phase. All records of a specified phase are processed before
|
||||
those with higher phase number. It is generally better practice to use linked
|
||||
passive records to enforce the order of processing rather than a phase number.
|
||||
If the PINI field is set to NO, the PHAS field is not meaningful for passive
|
||||
records.
|
||||
|
||||
The B<EVNT> field specifies an event number. This event number is used if the
|
||||
SCAN field is set to C<Event>. All records with scan type C<Event> and the
|
||||
same EVNT value will be processed when a call to post_event for EVNT is made.
|
||||
The call to post_event is: post_event(short event_number).
|
||||
The B<EVNT> field specifies an event name or number. This event identifier is
|
||||
used if the SCAN field is set to C<Event>. All records with scan type C<Event>
|
||||
and the same EVNT value will be processed when that event is signalled by other
|
||||
software running in the IOC, either by calling C<post_event(int event)> for
|
||||
numeric events, or by calling C<postEvent()> with an event handle previously
|
||||
looked up using C<eventNameToHandle()> for named events.
|
||||
|
||||
The B<PRIO> field specifies the scheduling priority for processing records
|
||||
with SCAN=C<I/O Event> and asynchronous record completion tasks.
|
||||
|
||||
@@ -154,7 +154,7 @@ static long dbConstLoadScalar(struct link *plink, short dbrType, void *pbuffer)
|
||||
const char *pstr = plink->value.constantStr;
|
||||
size_t len;
|
||||
|
||||
if (!pstr)
|
||||
if (!pstr || !pstr[0])
|
||||
return S_db_badField;
|
||||
len = strlen(pstr);
|
||||
|
||||
@@ -181,7 +181,7 @@ static long dbConstLoadLS(struct link *plink, char *pbuffer, epicsUInt32 size,
|
||||
const char *pstr = plink->value.constantStr;
|
||||
long status;
|
||||
|
||||
if (!pstr)
|
||||
if (!pstr || !pstr[0])
|
||||
return S_db_badField;
|
||||
|
||||
status = dbLSConvertJSON(pstr, pbuffer, size, plen);
|
||||
@@ -197,7 +197,7 @@ static long dbConstLoadArray(struct link *plink, short dbrType, void *pbuffer,
|
||||
const char *pstr = plink->value.constantStr;
|
||||
long status;
|
||||
|
||||
if (!pstr)
|
||||
if (!pstr || !pstr[0])
|
||||
return S_db_badField;
|
||||
|
||||
/* Choice values must be numeric */
|
||||
|
||||
@@ -76,6 +76,7 @@ struct event_que {
|
||||
unsigned short quota; /* the number of assigned entries*/
|
||||
unsigned short nDuplicates; /* N events duplicated on this q */
|
||||
unsigned short nCanceled; /* the number of canceled entries */
|
||||
unsigned possibleStall;
|
||||
};
|
||||
|
||||
struct event_user {
|
||||
@@ -934,6 +935,7 @@ void db_post_single_event (dbEventSubscription event)
|
||||
static int event_read ( struct event_que *ev_que )
|
||||
{
|
||||
db_field_log *pfl;
|
||||
int notifiedRemaining = 0;
|
||||
void ( *user_sub ) ( void *user_arg, struct dbChannel *chan,
|
||||
int eventsRemaining, db_field_log *pfl );
|
||||
|
||||
@@ -955,6 +957,7 @@ static int event_read ( struct event_que *ev_que )
|
||||
|
||||
while ( ev_que->evque[ev_que->getix] != EVENTQEMPTY ) {
|
||||
struct evSubscrip *pevent = ev_que->evque[ev_que->getix];
|
||||
int eventsRemaining;
|
||||
|
||||
pfl = ev_que->valque[ev_que->getix];
|
||||
if ( pevent == &canceledEvent ) {
|
||||
@@ -977,6 +980,7 @@ static int event_read ( struct event_que *ev_que )
|
||||
|
||||
event_remove ( ev_que, ev_que->getix, EVENTQEMPTY );
|
||||
ev_que->getix = RNGINC ( ev_que->getix );
|
||||
eventsRemaining = ev_que->evque[ev_que->getix] != EVENTQEMPTY && !ev_que->nCanceled;
|
||||
|
||||
/*
|
||||
* create a local copy of the call back parameters while
|
||||
@@ -1009,7 +1013,8 @@ static int event_read ( struct event_que *ev_que )
|
||||
if (pfl) {
|
||||
/* Issue user callback */
|
||||
( *user_sub ) ( pevent->user_arg, pevent->chan,
|
||||
ev_que->evque[ev_que->getix] != EVENTQEMPTY, pfl );
|
||||
eventsRemaining, pfl );
|
||||
notifiedRemaining = eventsRemaining;
|
||||
}
|
||||
LOCKEVQUE (ev_que);
|
||||
|
||||
@@ -1036,6 +1041,11 @@ static int event_read ( struct event_que *ev_que )
|
||||
db_delete_field_log(pfl);
|
||||
}
|
||||
|
||||
if(notifiedRemaining && !ev_que->possibleStall) {
|
||||
ev_que->possibleStall = 1;
|
||||
errlogPrintf(ERL_WARNING " dbEvent possible queue stall\n");
|
||||
}
|
||||
|
||||
UNLOCKEVQUE (ev_que);
|
||||
|
||||
return DB_EVENT_OK;
|
||||
|
||||
@@ -410,8 +410,21 @@ DBCORE_API long dbLoadLinkArray(struct link *, short dbrType, void *pbuffer,
|
||||
DBCORE_API long dbGetNelements(const struct link *plink, long *pnElements);
|
||||
DBCORE_API int dbIsLinkConnected(const struct link *plink); /* 0 or 1 */
|
||||
DBCORE_API int dbGetLinkDBFtype(const struct link *plink);
|
||||
/** \brief Fetch current value from link.
|
||||
* \param dbrType Database DBR code
|
||||
* \param pbuffer Destination buffer
|
||||
* \param nRequest If !NULL. Caller initializes with number of elements requested,
|
||||
* On success, set to number of elements written to pbuffer.
|
||||
* \return 0 on success
|
||||
*
|
||||
* When called with `nRequest==NULL`, treated as a request for one (1)
|
||||
* element.
|
||||
*
|
||||
* see lset::getValue
|
||||
*/
|
||||
DBCORE_API long dbTryGetLink(struct link *, short dbrType, void *pbuffer,
|
||||
long *nRequest);
|
||||
/** see dbTryGetLink() */
|
||||
DBCORE_API long dbGetLink(struct link *, short dbrType, void *pbuffer,
|
||||
long *options, long *nRequest);
|
||||
DBCORE_API long dbGetControlLimits(const struct link *plink, double *low,
|
||||
|
||||
@@ -168,9 +168,13 @@ void scanStop(void)
|
||||
epicsEventSignal(ppsl->loopEvent);
|
||||
epicsEventWait(startStopEvent);
|
||||
}
|
||||
for (i = 0; i < nPeriodic; i++) {
|
||||
epicsThreadMustJoin(periodicTaskId[i]);
|
||||
}
|
||||
|
||||
scanOnce((dbCommon *)&exitOnce);
|
||||
epicsEventWait(startStopEvent);
|
||||
epicsThreadMustJoin(onceTaskId);
|
||||
}
|
||||
|
||||
void scanCleanup(void)
|
||||
@@ -761,14 +765,16 @@ void scanOnceQueueShow(const int reset)
|
||||
|
||||
static void initOnce(void)
|
||||
{
|
||||
epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
|
||||
opts.joinable = 1;
|
||||
opts.priority = epicsThreadPriorityScanLow + nPeriodic;
|
||||
opts.stackSize = epicsThreadStackBig;
|
||||
if ((onceQ = epicsRingBytesLockedCreate(sizeof(onceEntry)*onceQueueSize)) == NULL) {
|
||||
cantProceed("initOnce: Ring buffer create failed\n");
|
||||
}
|
||||
if(!onceSem)
|
||||
onceSem = epicsEventMustCreate(epicsEventEmpty);
|
||||
onceTaskId = epicsThreadCreate("scanOnce",
|
||||
epicsThreadPriorityScanLow + nPeriodic,
|
||||
epicsThreadGetStackSize(epicsThreadStackBig), onceTask, 0);
|
||||
onceTaskId = epicsThreadCreateOpt("scanOnce", onceTask, 0, &opts);
|
||||
|
||||
epicsEventWait(startStopEvent);
|
||||
}
|
||||
@@ -932,14 +938,16 @@ static void spawnPeriodic(int ind)
|
||||
{
|
||||
periodic_scan_list *ppsl = papPeriodic[ind];
|
||||
char taskName[20];
|
||||
epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
|
||||
opts.joinable = 1;
|
||||
opts.priority = epicsThreadPriorityScanLow + ind;
|
||||
opts.stackSize = epicsThreadStackBig;
|
||||
|
||||
if (!ppsl) return;
|
||||
|
||||
sprintf(taskName, "scan-%g", ppsl->period);
|
||||
periodicTaskId[ind] = epicsThreadCreate(
|
||||
taskName, epicsThreadPriorityScanLow + ind,
|
||||
epicsThreadGetStackSize(epicsThreadStackBig),
|
||||
periodicTask, (void *)ppsl);
|
||||
periodicTaskId[ind] = epicsThreadCreateOpt(
|
||||
taskName, periodicTask, (void *)ppsl, &opts);
|
||||
|
||||
epicsEventWait(startStopEvent);
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsign
|
||||
break;
|
||||
}
|
||||
#define OP(DBR,Type,pat) case DBR: {Type expect = *(Type*)pbuf, actual = *(Type*)gbuf; assert(vSize==sizeof(Type)); match &= expect==actual; \
|
||||
if(expect!=actual) testDiag("[%lu] expected=" pat " actual=" pat, n, expect, actual); break;}
|
||||
if(expect!=actual) {testDiag("[%lu] expected=" pat " actual=" pat, n, expect, actual);} break;}
|
||||
|
||||
OP(DBR_CHAR, char, "%c");
|
||||
OP(DBR_UCHAR, unsigned char, "%u");
|
||||
|
||||
@@ -39,12 +39,17 @@ extern "C" {
|
||||
* will adjust automatically, it just compares field sizes.
|
||||
*/
|
||||
union native_value {
|
||||
epicsInt8 dbf_char;
|
||||
epicsInt16 dbf_short;
|
||||
epicsEnum16 dbf_enum;
|
||||
epicsInt32 dbf_long;
|
||||
epicsFloat32 dbf_float;
|
||||
epicsFloat64 dbf_double;
|
||||
epicsInt8 dbf_char;
|
||||
epicsUInt8 dbf_uchar;
|
||||
epicsInt16 dbf_short;
|
||||
epicsUInt16 dbf_ushort;
|
||||
epicsEnum16 dbf_enum;
|
||||
epicsInt32 dbf_long;
|
||||
epicsUInt32 dbf_ulong;
|
||||
epicsInt64 dbf_int64;
|
||||
epicsUInt64 dbf_uint64;
|
||||
epicsFloat32 dbf_float;
|
||||
epicsFloat64 dbf_double;
|
||||
#ifdef DB_EVENT_LOG_STRINGS
|
||||
char dbf_string[MAX_STRING_SIZE];
|
||||
#endif
|
||||
|
||||
@@ -75,7 +75,7 @@ DBCORE_API void recGblInheritSevr(int msMode, void *precord, epicsEnum16 stat,
|
||||
epicsEnum16 sevr);
|
||||
DBCORE_API int recGblSetSevrMsg(void *precord, epicsEnum16 new_stat,
|
||||
epicsEnum16 new_sevr,
|
||||
const char *msg, ...) EPICS_PRINTF_STYLE(4,5);
|
||||
EPICS_PRINTF_FMT(const char *msg), ...) EPICS_PRINTF_STYLE(4,5);
|
||||
DBCORE_API int recGblSetSevrVMsg(void *precord, epicsEnum16 new_stat,
|
||||
epicsEnum16 new_sevr,
|
||||
const char *msg, va_list args);
|
||||
|
||||
@@ -139,14 +139,16 @@ char** dbCompleteRecord(const char *cword)
|
||||
|
||||
if(!prefix.empty() || !suggestions.empty()) {
|
||||
ret = (char**)malloc(sizeof(*ret)*(2u + suggestions.size()));
|
||||
ret[0] = prefix.dup();
|
||||
size_t n=1u;
|
||||
for(suggestions_t::iterator it(suggestions.begin()), end(suggestions.end());
|
||||
it!=end; ++it)
|
||||
{
|
||||
ret[n++] = it->dup();
|
||||
if(ret) {
|
||||
ret[0] = prefix.dup();
|
||||
size_t n=1u;
|
||||
for(suggestions_t::iterator it(suggestions.begin()), end(suggestions.end());
|
||||
it!=end; ++it)
|
||||
{
|
||||
ret[n++] = it->dup();
|
||||
}
|
||||
ret[n] = NULL;
|
||||
}
|
||||
ret[n] = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -111,7 +111,8 @@ typedef struct inputFile{
|
||||
static ELLLIST inputFileList = ELLLIST_INIT;
|
||||
|
||||
static inputFile *pinputFileNow = NULL;
|
||||
static DBBASE *pdbbase = NULL;
|
||||
/* The DBBASE most recently allocated/used by dbReadCOM() */
|
||||
static DBBASE *savedPdbbase = NULL;
|
||||
|
||||
typedef struct tempListNode {
|
||||
ELLNODE node;
|
||||
@@ -233,15 +234,15 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
|
||||
}
|
||||
|
||||
if(*ppdbbase == 0) *ppdbbase = dbAllocBase();
|
||||
pdbbase = *ppdbbase;
|
||||
savedPdbbase = *ppdbbase;
|
||||
if(path && strlen(path)>0) {
|
||||
dbPath(pdbbase,path);
|
||||
dbPath(savedPdbbase,path);
|
||||
} else {
|
||||
penv = getenv("EPICS_DB_INCLUDE_PATH");
|
||||
if(penv) {
|
||||
dbPath(pdbbase,penv);
|
||||
dbPath(savedPdbbase,penv);
|
||||
} else {
|
||||
dbPath(pdbbase,".");
|
||||
dbPath(savedPdbbase,".");
|
||||
}
|
||||
}
|
||||
my_buffer = dbCalloc(MY_BUFFER_SIZE,sizeof(char));
|
||||
@@ -271,7 +272,7 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
|
||||
FILE *fp1 = 0;
|
||||
|
||||
if (pinputFile->filename)
|
||||
pinputFile->path = dbOpenFile(pdbbase, pinputFile->filename, &fp1);
|
||||
pinputFile->path = dbOpenFile(savedPdbbase, pinputFile->filename, &fp1);
|
||||
if (!pinputFile->filename || !fp1) {
|
||||
errPrintf(0, __FILE__, __LINE__,
|
||||
"dbRead opening file %s\n",pinputFile->filename);
|
||||
@@ -297,13 +298,13 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
|
||||
while (ellCount(&tempList))
|
||||
popFirstTemp(); /* Memory leak on parser failure */
|
||||
|
||||
dbFreePath(pdbbase);
|
||||
dbFreePath(savedPdbbase);
|
||||
if(!status) { /*add RTYP and VERS as an attribute */
|
||||
DBENTRY dbEntry;
|
||||
DBENTRY *pdbEntry = &dbEntry;
|
||||
long localStatus;
|
||||
|
||||
dbInitEntry(pdbbase,pdbEntry);
|
||||
dbInitEntry(savedPdbbase,pdbEntry);
|
||||
localStatus = dbFirstRecordType(pdbEntry);
|
||||
while(!localStatus) {
|
||||
localStatus = dbPutRecordAttribute(pdbEntry,"RTYP",
|
||||
@@ -323,7 +324,7 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
|
||||
cleanup:
|
||||
if(dbRecordsAbcSorted) {
|
||||
ELLNODE *cur;
|
||||
for(cur = ellFirst(&pdbbase->recordTypeList); cur; cur=ellNext(cur))
|
||||
for(cur = ellFirst(&savedPdbbase->recordTypeList); cur; cur=ellNext(cur))
|
||||
{
|
||||
dbRecordType *rtype = CONTAINER(cur, dbRecordType, node);
|
||||
|
||||
@@ -416,12 +417,12 @@ static void dbIncludePrint(void)
|
||||
|
||||
static void dbPathCmd(char *path)
|
||||
{
|
||||
dbPath(pdbbase,path);
|
||||
dbPath(savedPdbbase,path);
|
||||
}
|
||||
|
||||
static void dbAddPathCmd(char *path)
|
||||
{
|
||||
dbAddPath(pdbbase,path);
|
||||
dbAddPath(savedPdbbase,path);
|
||||
}
|
||||
|
||||
static void dbIncludeNew(char *filename)
|
||||
@@ -431,7 +432,7 @@ static void dbIncludeNew(char *filename)
|
||||
|
||||
pinputFile = dbCalloc(1,sizeof(inputFile));
|
||||
pinputFile->filename = macEnvExpand(filename);
|
||||
pinputFile->path = dbOpenFile(pdbbase, pinputFile->filename, &fp);
|
||||
pinputFile->path = dbOpenFile(savedPdbbase, pinputFile->filename, &fp);
|
||||
if (!fp) {
|
||||
epicsPrintf("Can't open include file \"%s\"\n", filename);
|
||||
yyerror(NULL);
|
||||
@@ -453,7 +454,7 @@ static void dbMenuHead(char *name)
|
||||
yyerrorAbort("dbMenuHead: Menu name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->menuList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->menuList);
|
||||
if(pgphentry) {
|
||||
duplicate = TRUE;
|
||||
return;
|
||||
@@ -501,14 +502,14 @@ static void dbMenuBody(void)
|
||||
}
|
||||
if(ellCount(&tempList)) yyerrorAbort("dbMenuBody: tempList not empty");
|
||||
/* Add menu in sorted order */
|
||||
pMenu = (dbMenu *)ellFirst(&pdbbase->menuList);
|
||||
pMenu = (dbMenu *)ellFirst(&savedPdbbase->menuList);
|
||||
while(pMenu && strcmp(pMenu->name,pnewMenu->name) >0 )
|
||||
pMenu = (dbMenu *)ellNext(&pMenu->node);
|
||||
if(pMenu)
|
||||
ellInsert(&pdbbase->menuList,ellPrevious(&pMenu->node),&pnewMenu->node);
|
||||
ellInsert(&savedPdbbase->menuList,ellPrevious(&pMenu->node),&pnewMenu->node);
|
||||
else
|
||||
ellAdd(&pdbbase->menuList,&pnewMenu->node);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pnewMenu->name,&pdbbase->menuList);
|
||||
ellAdd(&savedPdbbase->menuList,&pnewMenu->node);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pnewMenu->name,&savedPdbbase->menuList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
} else {
|
||||
@@ -525,14 +526,14 @@ static void dbRecordtypeHead(char *name)
|
||||
yyerrorAbort("dbRecordtypeHead: Recordtype name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->recordTypeList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->recordTypeList);
|
||||
if(pgphentry) {
|
||||
duplicate = TRUE;
|
||||
return;
|
||||
}
|
||||
pdbRecordType = dbCalloc(1,sizeof(dbRecordType));
|
||||
pdbRecordType->name = epicsStrDup(name);
|
||||
if (pdbbase->loadCdefs) ellInit(&pdbRecordType->cdefList);
|
||||
if (savedPdbbase->loadCdefs) ellInit(&pdbRecordType->cdefList);
|
||||
if(ellCount(&tempList))
|
||||
yyerrorAbort("dbRecordtypeHead tempList not empty");
|
||||
allocTemp(pdbRecordType);
|
||||
@@ -564,13 +565,13 @@ static short findOrAddGuiGroup(const char *name)
|
||||
{
|
||||
dbGuiGroup *pdbGuiGroup;
|
||||
GPHENTRY *pgphentry;
|
||||
pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->guiGroupList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash, name, &savedPdbbase->guiGroupList);
|
||||
if (!pgphentry) {
|
||||
pdbGuiGroup = dbCalloc(1,sizeof(dbGuiGroup));
|
||||
pdbGuiGroup->name = epicsStrDup(name);
|
||||
ellAdd(&pdbbase->guiGroupList, &pdbGuiGroup->node);
|
||||
pdbGuiGroup->key = ellCount(&pdbbase->guiGroupList);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash, pdbGuiGroup->name, &pdbbase->guiGroupList);
|
||||
ellAdd(&savedPdbbase->guiGroupList, &pdbGuiGroup->node);
|
||||
pdbGuiGroup->key = ellCount(&savedPdbbase->guiGroupList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash, pdbGuiGroup->name, &savedPdbbase->guiGroupList);
|
||||
pgphentry->userPvt = pdbGuiGroup;
|
||||
}
|
||||
return ((dbGuiGroup *)pgphentry->userPvt)->key;
|
||||
@@ -653,8 +654,8 @@ static void dbRecordtypeFieldItem(char *name,char *value)
|
||||
return;
|
||||
}
|
||||
if(strcmp(name,"menu")==0) {
|
||||
pdbFldDes->ftPvt = (dbMenu *)dbFindMenu(pdbbase,value);
|
||||
if(!pdbbase->ignoreMissingMenus && !pdbFldDes->ftPvt)
|
||||
pdbFldDes->ftPvt = (dbMenu *)dbFindMenu(savedPdbbase,value);
|
||||
if(!savedPdbbase->ignoreMissingMenus && !pdbFldDes->ftPvt)
|
||||
yyerrorAbort("menu not found");
|
||||
return;
|
||||
}
|
||||
@@ -672,7 +673,7 @@ static void dbRecordtypeCdef(char *text) {
|
||||
tempListNode *ptempListNode;
|
||||
dbRecordType *pdbRecordType;
|
||||
|
||||
if (!pdbbase->loadCdefs || duplicate) return;
|
||||
if (!savedPdbbase->loadCdefs || duplicate) return;
|
||||
ptempListNode = (tempListNode *)ellFirst(&tempList);
|
||||
pdbRecordType = ptempListNode->item;
|
||||
|
||||
@@ -781,14 +782,14 @@ static void dbRecordtypeBody(void)
|
||||
ellInit(&pdbRecordType->attributeList);
|
||||
ellInit(&pdbRecordType->recList);
|
||||
ellInit(&pdbRecordType->devList);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pdbRecordType->name,
|
||||
&pdbbase->recordTypeList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pdbRecordType->name,
|
||||
&savedPdbbase->recordTypeList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
} else {
|
||||
pgphentry->userPvt = pdbRecordType;
|
||||
}
|
||||
ellAdd(&pdbbase->recordTypeList,&pdbRecordType->node);
|
||||
ellAdd(&savedPdbbase->recordTypeList,&pdbRecordType->node);
|
||||
}
|
||||
|
||||
static void dbDevice(char *recordtype,char *linktype,
|
||||
@@ -798,7 +799,7 @@ static void dbDevice(char *recordtype,char *linktype,
|
||||
dbRecordType *pdbRecordType;
|
||||
GPHENTRY *pgphentry;
|
||||
int i,link_type;
|
||||
pgphentry = gphFind(pdbbase->pgpHash,recordtype,&pdbbase->recordTypeList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,recordtype,&savedPdbbase->recordTypeList);
|
||||
if(!pgphentry) {
|
||||
epicsPrintf("Record type \"%s\" not found for device \"%s\"\n",
|
||||
recordtype, choicestring);
|
||||
@@ -819,7 +820,7 @@ static void dbDevice(char *recordtype,char *linktype,
|
||||
return;
|
||||
}
|
||||
pdbRecordType = (dbRecordType *)pgphentry->userPvt;
|
||||
pgphentry = gphFind(pdbbase->pgpHash,choicestring,&pdbRecordType->devList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,choicestring,&pdbRecordType->devList);
|
||||
if(pgphentry) {
|
||||
return;
|
||||
}
|
||||
@@ -827,7 +828,7 @@ static void dbDevice(char *recordtype,char *linktype,
|
||||
pdevSup->name = epicsStrDup(dsetname);
|
||||
pdevSup->choice = epicsStrDup(choicestring);
|
||||
pdevSup->link_type = link_type;
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pdevSup->choice,&pdbRecordType->devList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pdevSup->choice,&pdbRecordType->devList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
} else {
|
||||
@@ -845,18 +846,18 @@ static void dbDriver(char *name)
|
||||
yyerrorAbort("dbDriver: Driver name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->drvList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->drvList);
|
||||
if(pgphentry) {
|
||||
return;
|
||||
}
|
||||
pdrvSup = dbCalloc(1,sizeof(drvSup));
|
||||
pdrvSup->name = epicsStrDup(name);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pdrvSup->name,&pdbbase->drvList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pdrvSup->name,&savedPdbbase->drvList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
}
|
||||
pgphentry->userPvt = pdrvSup;
|
||||
ellAdd(&pdbbase->drvList,&pdrvSup->node);
|
||||
ellAdd(&savedPdbbase->drvList,&pdrvSup->node);
|
||||
}
|
||||
|
||||
static void dbLinkType(char *name, char *jlif_name)
|
||||
@@ -864,19 +865,19 @@ static void dbLinkType(char *name, char *jlif_name)
|
||||
linkSup *pLinkSup;
|
||||
GPHENTRY *pgphentry;
|
||||
|
||||
pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->linkList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash, name, &savedPdbbase->linkList);
|
||||
if (pgphentry) {
|
||||
return;
|
||||
}
|
||||
pLinkSup = dbCalloc(1,sizeof(linkSup));
|
||||
pLinkSup->name = epicsStrDup(name);
|
||||
pLinkSup->jlif_name = epicsStrDup(jlif_name);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash, pLinkSup->name, &pdbbase->linkList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash, pLinkSup->name, &savedPdbbase->linkList);
|
||||
if (!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
}
|
||||
pgphentry->userPvt = pLinkSup;
|
||||
ellAdd(&pdbbase->linkList, &pLinkSup->node);
|
||||
ellAdd(&savedPdbbase->linkList, &pLinkSup->node);
|
||||
}
|
||||
|
||||
static void dbRegistrar(char *name)
|
||||
@@ -888,18 +889,18 @@ static void dbRegistrar(char *name)
|
||||
yyerrorAbort("dbRegistrar: Registrar name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->registrarList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->registrarList);
|
||||
if(pgphentry) {
|
||||
return;
|
||||
}
|
||||
ptext = dbCalloc(1,sizeof(dbText));
|
||||
ptext->text = epicsStrDup(name);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,ptext->text,&pdbbase->registrarList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,ptext->text,&savedPdbbase->registrarList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
}
|
||||
pgphentry->userPvt = ptext;
|
||||
ellAdd(&pdbbase->registrarList,&ptext->node);
|
||||
ellAdd(&savedPdbbase->registrarList,&ptext->node);
|
||||
}
|
||||
|
||||
static void dbFunction(char *name)
|
||||
@@ -911,18 +912,18 @@ static void dbFunction(char *name)
|
||||
yyerrorAbort("dbFunction: Function name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->functionList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->functionList);
|
||||
if(pgphentry) {
|
||||
return;
|
||||
}
|
||||
ptext = dbCalloc(1,sizeof(dbText));
|
||||
ptext->text = epicsStrDup(name);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,ptext->text,&pdbbase->functionList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,ptext->text,&savedPdbbase->functionList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
}
|
||||
pgphentry->userPvt = ptext;
|
||||
ellAdd(&pdbbase->functionList,&ptext->node);
|
||||
ellAdd(&savedPdbbase->functionList,&ptext->node);
|
||||
}
|
||||
|
||||
static void dbVariable(char *name, char *type)
|
||||
@@ -934,19 +935,19 @@ static void dbVariable(char *name, char *type)
|
||||
yyerrorAbort("dbVariable: Variable name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->variableList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->variableList);
|
||||
if(pgphentry) {
|
||||
return;
|
||||
}
|
||||
pvar = dbCalloc(1,sizeof(dbVariableDef));
|
||||
pvar->name = epicsStrDup(name);
|
||||
pvar->type = epicsStrDup(type);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pvar->name,&pdbbase->variableList);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pvar->name,&savedPdbbase->variableList);
|
||||
if(!pgphentry) {
|
||||
yyerrorAbort("gphAdd failed");
|
||||
}
|
||||
pgphentry->userPvt = pvar;
|
||||
ellAdd(&pdbbase->variableList,&pvar->node);
|
||||
ellAdd(&savedPdbbase->variableList,&pvar->node);
|
||||
}
|
||||
|
||||
static void dbBreakHead(char *name)
|
||||
@@ -958,7 +959,7 @@ static void dbBreakHead(char *name)
|
||||
yyerrorAbort("dbBreakHead: Breaktable name can't be empty");
|
||||
return;
|
||||
}
|
||||
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->bptList);
|
||||
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->bptList);
|
||||
if(pgphentry) {
|
||||
duplicate = TRUE;
|
||||
return;
|
||||
@@ -1042,17 +1043,17 @@ static void dbBreakBody(void)
|
||||
/* Continue with last slope beyond the final point */
|
||||
paBrkInt[number-1].slope = paBrkInt[number-2].slope;
|
||||
/* Add brkTable in sorted order */
|
||||
pbrkTable = (brkTable *)ellFirst(&pdbbase->bptList);
|
||||
pbrkTable = (brkTable *)ellFirst(&savedPdbbase->bptList);
|
||||
while (pbrkTable) {
|
||||
if (strcmp(pbrkTable->name, pnewbrkTable->name) > 0) {
|
||||
ellInsert(&pdbbase->bptList, ellPrevious((ELLNODE *)pbrkTable),
|
||||
ellInsert(&savedPdbbase->bptList, ellPrevious((ELLNODE *)pbrkTable),
|
||||
(ELLNODE *)pnewbrkTable);
|
||||
break;
|
||||
}
|
||||
pbrkTable = (brkTable *)ellNext(&pbrkTable->node);
|
||||
}
|
||||
if (!pbrkTable) ellAdd(&pdbbase->bptList, &pnewbrkTable->node);
|
||||
pgphentry = gphAdd(pdbbase->pgpHash,pnewbrkTable->name,&pdbbase->bptList);
|
||||
if (!pbrkTable) ellAdd(&savedPdbbase->bptList, &pnewbrkTable->node);
|
||||
pgphentry = gphAdd(savedPdbbase->pgpHash,pnewbrkTable->name,&savedPdbbase->bptList);
|
||||
if (!pgphentry) {
|
||||
yyerrorAbort("dbBreakBody: gphAdd failed");
|
||||
return;
|
||||
@@ -1103,7 +1104,7 @@ static void dbRecordHead(char *recordType, char *name, int visible)
|
||||
if(dbRecordNameValidate(name))
|
||||
return;
|
||||
|
||||
pdbentry = dbAllocEntry(pdbbase);
|
||||
pdbentry = dbAllocEntry(savedPdbbase);
|
||||
if (ellCount(&tempList))
|
||||
yyerrorAbort("dbRecordHead: tempList not empty");
|
||||
allocTemp(pdbentry);
|
||||
@@ -1257,10 +1258,10 @@ static void dbAlias(char *name, char *alias)
|
||||
DBENTRY dbEntry;
|
||||
DBENTRY *pdbEntry = &dbEntry;
|
||||
|
||||
if(dbRecordNameValidate(alias))
|
||||
if(dbRecordNameValidate(alias) || dbRecordNameValidate(name))
|
||||
return;
|
||||
|
||||
dbInitEntry(pdbbase, pdbEntry);
|
||||
dbInitEntry(savedPdbbase, pdbEntry);
|
||||
if (dbFindRecord(pdbEntry, name)) {
|
||||
epicsPrintf("Alias \"%s\" refers to unknown record \"%s\"\n",
|
||||
alias, name);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
\*************************************************************************/
|
||||
|
||||
#include "iocsh.h"
|
||||
#include "errSymTbl.h"
|
||||
|
||||
#include "dbStaticIocRegister.h"
|
||||
#include "dbStaticLib.h"
|
||||
@@ -217,6 +218,40 @@ static void dbReportDeviceConfigCallFunc(const iocshArgBuf *args)
|
||||
dbReportDeviceConfig(*iocshPpdbbase,stdout);
|
||||
}
|
||||
|
||||
|
||||
static const iocshArg dbCreateAliasArg0 = { "record",iocshArgStringRecord};
|
||||
static const iocshArg dbCreateAliasArg1 = { "alias",iocshArgStringRecord};
|
||||
static const iocshArg * const dbCreateAliasArgs[] = {&argPdbbase,&dbCreateAliasArg0, &dbCreateAliasArg1};
|
||||
static const iocshFuncDef dbCreateAliasFuncDef = {
|
||||
"dbCreateAlias",
|
||||
3,
|
||||
dbCreateAliasArgs,
|
||||
"Add a new record alias.\n"
|
||||
"\n"
|
||||
"Example: dbCreateAlias pdbbase record:name new:alias\n",
|
||||
};
|
||||
static void dbCreateAliasCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
DBENTRY ent;
|
||||
long status;
|
||||
|
||||
dbInitEntry(*iocshPpdbbase, &ent);
|
||||
if(!args[1].sval || !args[2].sval) {
|
||||
status = S_dbLib_recNotFound;
|
||||
|
||||
} else {
|
||||
status = dbFindRecord(&ent, args[1].sval);
|
||||
if(!status) {
|
||||
status = dbCreateAlias(&ent, args[2].sval);
|
||||
}
|
||||
}
|
||||
dbFinishEntry(&ent);
|
||||
if(status) {
|
||||
fprintf(stderr, "Error: %ld %s\n", status, errSymMsg(status));
|
||||
iocshSetError(1);
|
||||
}
|
||||
}
|
||||
|
||||
void dbStaticIocRegister(void)
|
||||
{
|
||||
iocshRegister(&dbDumpPathFuncDef, dbDumpPathCallFunc);
|
||||
@@ -234,4 +269,5 @@ void dbStaticIocRegister(void)
|
||||
iocshRegister(&dbPvdDumpFuncDef, dbPvdDumpCallFunc);
|
||||
iocshRegister(&dbPvdTableSizeFuncDef,dbPvdTableSizeCallFunc);
|
||||
iocshRegister(&dbReportDeviceConfigFuncDef, dbReportDeviceConfigCallFunc);
|
||||
iocshRegister(&dbCreateAliasFuncDef, dbCreateAliasCallFunc);
|
||||
}
|
||||
|
||||
@@ -1652,6 +1652,7 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
|
||||
dbRecordNode *pnewnode;
|
||||
DBENTRY tempEntry;
|
||||
PVDENTRY *ppvd;
|
||||
long status;
|
||||
|
||||
if (!precordType)
|
||||
return S_dbLib_recordTypeNotFound;
|
||||
@@ -1664,9 +1665,10 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
|
||||
return S_dbLib_recNotFound;
|
||||
|
||||
dbInitEntry(pdbentry->pdbbase, &tempEntry);
|
||||
if (!dbFindRecord(&tempEntry, alias))
|
||||
return S_dbLib_recExists;
|
||||
status = dbFindRecord(&tempEntry, alias);
|
||||
dbFinishEntry(&tempEntry);
|
||||
if (!status)
|
||||
return S_dbLib_recExists;
|
||||
|
||||
pnewnode = dbCalloc(1, sizeof(dbRecordNode));
|
||||
pnewnode->recordname = epicsStrDup(alias);
|
||||
@@ -1676,15 +1678,16 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
|
||||
precnode->flags |= DBRN_FLAGS_HASALIAS;
|
||||
ellInit(&pnewnode->infoList);
|
||||
|
||||
ellAdd(&precordType->recList, &pnewnode->node);
|
||||
precordType->no_aliases++;
|
||||
|
||||
ppvd = dbPvdAdd(pdbentry->pdbbase, precordType, pnewnode);
|
||||
if (!ppvd) {
|
||||
errMessage(-1, "dbCreateAlias: Add to PVD failed");
|
||||
free(pnewnode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ellAdd(&precordType->recList, &pnewnode->node);
|
||||
precordType->no_aliases++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ char *dbRecordName(DBENTRY *pdbentry);
|
||||
char *dbGetStringNum(DBENTRY *pdbentry);
|
||||
long dbPutStringNum(DBENTRY *pdbentry,const char *pstring);
|
||||
|
||||
void dbMsgPrint(DBENTRY *pdbentry, const char *fmt, ...) EPICS_PRINTF_STYLE(2,3);
|
||||
void dbMsgPrint(
|
||||
DBENTRY *pdbentry, EPICS_PRINTF_FMT(const char *fmt), ...
|
||||
) EPICS_PRINTF_STYLE(2,3);
|
||||
|
||||
void dbPutStringSuggest(DBENTRY *pdbentry, const char *pstring);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
SRC_DIRS += $(IOCDIR)/dbtemplate
|
||||
|
||||
PROD_HOST += msi
|
||||
PROD_CMD += msi
|
||||
|
||||
msi_SRCS = msi.cpp
|
||||
msi_LIBS += Com
|
||||
|
||||
@@ -716,13 +716,13 @@ int iocShutdown(void)
|
||||
iterateRecords(doCloseLinks, NULL);
|
||||
initHookAnnounce(initHookAfterCloseLinks);
|
||||
|
||||
if (iocBuildMode == buildIsolated) {
|
||||
/* stop and "join" threads */
|
||||
scanStop();
|
||||
initHookAnnounce(initHookAfterStopScan);
|
||||
callbackStop();
|
||||
initHookAnnounce(initHookAfterStopCallback);
|
||||
} else {
|
||||
/* stop and "join" threads */
|
||||
scanStop();
|
||||
initHookAnnounce(initHookAfterStopScan);
|
||||
callbackStop();
|
||||
initHookAnnounce(initHookAfterStopCallback);
|
||||
|
||||
if (iocBuildMode != buildIsolated) {
|
||||
dbStopServers();
|
||||
}
|
||||
|
||||
|
||||
@@ -216,22 +216,96 @@ registrar(tsInitialize)
|
||||
|
||||
=head3 TimeStamp Filter C<"ts">
|
||||
|
||||
This filter replaces the timestamp in the data fetched through the channel with
|
||||
the time the value was fetched (or an update was sent). The record's timestamp
|
||||
This filter is used for two purposes:
|
||||
|
||||
=over
|
||||
|
||||
=item * to retrieve the timestamp of the record as a value in several different
|
||||
formats;
|
||||
|
||||
=item * to retrieve the record value as normal, but replace the timestamp with
|
||||
the time the value was fetched.
|
||||
|
||||
=back
|
||||
|
||||
=head4 Parameters
|
||||
|
||||
=head4 No parameters (an empty pair of braces)
|
||||
|
||||
Retrieve the record value as normal, but replace the timestamp with the time the
|
||||
value was fetched (or an update was sent). This is useful for clients that can't
|
||||
handle timestamps that are far in the past. Normally, the record's timestamp
|
||||
indicates when the record last processed, which could have been days or even
|
||||
weeks ago for some records, or set to the EPICS epoch if the record has never
|
||||
processed.
|
||||
|
||||
=head4 Parameters
|
||||
=head4 Numeric type C<"num">
|
||||
|
||||
None, use an empty pair of braces.
|
||||
The following values are accepted for this parameter:
|
||||
|
||||
=head4 Example
|
||||
=over
|
||||
|
||||
Hal$ caget -a 'test:channel.{"ts":{}}'
|
||||
test:channel.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
|
||||
Hal$ caget -a 'test:channel'
|
||||
test:channel <undefined> 0 UDF INVALID
|
||||
=item * C<"dbl"> requests the timestamp as C<epicsFloat64> representing the
|
||||
non-integral number of seconds since epoch. This format is convenient,
|
||||
but loses precision, depending on which epoch is used.
|
||||
|
||||
=item * C<"sec"> requests the number of seconds since epoch as C<epicsUInt32>.
|
||||
|
||||
=item * C<"nsec"> requests the number of nanoseconds since epoch as
|
||||
C<epicsUInt32>.
|
||||
|
||||
=item * C<"ts"> requests the entire timestamp. It is provided as a two-element
|
||||
array of C<epicsUInt32> representing seconds and nanoseconds.
|
||||
|
||||
=back
|
||||
|
||||
Note that C<epicsUInt32> cannot be transferred over Channel Access; in that
|
||||
case, the value will be converted to C<epicsFloat64>.
|
||||
|
||||
=head4 String type C<"str">
|
||||
|
||||
The following values are accepted for this parameter:
|
||||
|
||||
=over
|
||||
|
||||
=item * C<"epics"> requests the timestamp as a string in the format used by
|
||||
tools such as C<caget>.
|
||||
|
||||
=item * C<"iso"> requests the timestamp as a string in the ISO8601 format.
|
||||
|
||||
=back
|
||||
|
||||
=head4 Epoch adjustment C<"epoch">
|
||||
|
||||
The following values are accepted for this parameter:
|
||||
|
||||
=over
|
||||
|
||||
=item * C<"epics"> keeps the EPICS epoch (1990-01-01) and is the default if the
|
||||
C<"epoch"> parameter is not specified.
|
||||
|
||||
=item * C<"unix"> converts the timestamp to the UNIX/POSIX epoch (1970-01-01).
|
||||
|
||||
=back
|
||||
|
||||
=head4 Examples
|
||||
|
||||
Hal$ caget -a 'test:invalid_ts.{"ts":{}}'
|
||||
test:invalid_ts.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
|
||||
Hal$ caget -a 'test:invalid_ts'
|
||||
test:invalid_ts <undefined> 0 UDF INVALID
|
||||
Hal$ caget -a test:channel
|
||||
test:channel 2021-03-11 18:23:48.265386 42
|
||||
Hal$ caget 'test:channel.{"ts": {"str": "epics"}}'
|
||||
test:channel.{"ts": {"str": "epics"}} 2021-03-11 18:23:48.265386
|
||||
Hal$ caget 'test:channel.{"ts": {"str": "iso"}}'
|
||||
test:channel.{"ts": {"str": "iso"}} 2021-03-11T18:23:48.265386+0100
|
||||
Hal$ caget -f9 'test:channel.{"ts": {"num": "dbl"}}'
|
||||
test:channel.{"ts": {"num": "dbl"}} 984331428.265386105
|
||||
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts"}}'
|
||||
test:channel.{"ts": {"num": "ts"}} 2 984331428.0 265386163.0
|
||||
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts", "epoch": "unix"}}'
|
||||
test:channel.{"ts": {"num": "ts", "epoch": "unix"}} 2 1615483428.0 265386163.0
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2021 Cosylab d.d
|
||||
* Copyright (c) 2010 Brookhaven National Laboratory.
|
||||
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
|
||||
* fuer Materialien und Energie GmbH.
|
||||
@@ -9,6 +10,7 @@
|
||||
|
||||
/*
|
||||
* Author: Ralph Lange <Ralph.Lange@bessy.de>
|
||||
* Author: Jure Varlec <jure.varlec@cosylab.com>
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -16,20 +18,124 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "chfPlugin.h"
|
||||
#include "db_field_log.h"
|
||||
#include "dbExtractArray.h"
|
||||
#include "dbLock.h"
|
||||
#include "db_field_log.h"
|
||||
#include "epicsExport.h"
|
||||
#include "freeList.h"
|
||||
#include "errlog.h"
|
||||
|
||||
/*
|
||||
* The size of the data is different for each channel, and can even
|
||||
* change at runtime, so a freeList doesn't make much sense here.
|
||||
*/
|
||||
/* Allocation size for freelists */
|
||||
#define ALLOC_NUM_ELEMENTS 32
|
||||
|
||||
#define logicErrorMessage() \
|
||||
errMessage(-1, "Logic error: invalid state encountered in ts filter")
|
||||
|
||||
/* Filter settings */
|
||||
|
||||
enum tsMode {
|
||||
tsModeInvalid = 0,
|
||||
tsModeGenerate = 1,
|
||||
tsModeDouble = 2,
|
||||
tsModeSec = 3,
|
||||
tsModeNsec = 4,
|
||||
tsModeArray = 5,
|
||||
tsModeString = 6,
|
||||
};
|
||||
|
||||
static const chfPluginEnumType ts_numeric_enum[] = {
|
||||
{"dbl", 2}, {"sec", 3}, {"nsec", 4}, {"ts", 5}};
|
||||
|
||||
enum tsEpoch {
|
||||
tsEpochEpics = 0,
|
||||
tsEpochUnix = 1,
|
||||
};
|
||||
|
||||
static const chfPluginEnumType ts_epoch_enum[] = {{"epics", 0}, {"unix", 1}};
|
||||
|
||||
enum tsString {
|
||||
tsStringInvalid = 0,
|
||||
tsStringEpics = 1,
|
||||
tsStringIso = 2,
|
||||
};
|
||||
|
||||
static const chfPluginEnumType ts_string_enum[] = {{"epics", 1}, {"iso", 2}};
|
||||
|
||||
typedef struct tsPrivate {
|
||||
enum tsMode mode;
|
||||
enum tsEpoch epoch;
|
||||
enum tsString str;
|
||||
} tsPrivate;
|
||||
|
||||
static const chfPluginArgDef ts_args[] = {
|
||||
chfEnum(tsPrivate, mode, "num", 0, 0, ts_numeric_enum),
|
||||
chfEnum(tsPrivate, epoch, "epoch", 0, 0, ts_epoch_enum),
|
||||
chfEnum(tsPrivate, str, "str", 0, 0, ts_string_enum),
|
||||
chfPluginArgEnd
|
||||
};
|
||||
|
||||
static int parse_finished(void *pvt) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
if (settings->str != tsStringInvalid) {
|
||||
settings->mode = tsModeString;
|
||||
#if defined _MSC_VER && _MSC_VER <= 1700
|
||||
// VS 2012 crashes in ISO mode, doesn't support timezones
|
||||
if (settings->str == tsStringIso) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
} else if (settings->mode == tsModeInvalid) {
|
||||
settings->mode = tsModeGenerate;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Allocation of filter settings */
|
||||
static void *private_free_list;
|
||||
|
||||
static void * allocPvt() {
|
||||
return freeListCalloc(private_free_list);
|
||||
}
|
||||
|
||||
static void freePvt(void *pvt) {
|
||||
freeListFree(private_free_list, pvt);
|
||||
}
|
||||
|
||||
|
||||
/* Allocation of two-element arrays for second+nanosecond pairs */
|
||||
static void *ts_array_free_list;
|
||||
|
||||
static void *allocTsArray() {
|
||||
return freeListCalloc(ts_array_free_list);
|
||||
}
|
||||
|
||||
static void freeTsArray(db_field_log *pfl) {
|
||||
freeListFree(ts_array_free_list, pfl->u.r.field);
|
||||
}
|
||||
|
||||
/* Allocation of strings */
|
||||
static void *string_free_list;
|
||||
|
||||
static void *allocString() {
|
||||
return freeListCalloc(string_free_list);
|
||||
}
|
||||
|
||||
static void freeString(db_field_log *pfl) {
|
||||
freeListFree(string_free_list, pfl->u.r.field);
|
||||
}
|
||||
|
||||
|
||||
/* The dtor for waveform data for the case when we have to copy it. */
|
||||
static void freeArray(db_field_log *pfl) {
|
||||
/*
|
||||
* The size of the data is different for each channel, and can even
|
||||
* change at runtime, so a freeList doesn't make much sense here.
|
||||
*/
|
||||
free(pfl->u.r.field);
|
||||
}
|
||||
|
||||
static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
static db_field_log* generate(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
epicsTimeStamp now;
|
||||
epicsTimeGetCurrent(&now);
|
||||
|
||||
@@ -44,7 +150,7 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
dbScanLock(dbChannelRecord(chan));
|
||||
dbChannelGetArrayInfo(chan, &pSource, &nSource, &offset);
|
||||
dbExtractArray(pSource, pTarget, pfl->field_size,
|
||||
nSource, pfl->no_elements, offset, 1);
|
||||
nSource, pfl->no_elements, offset, 1);
|
||||
pfl->u.r.field = pTarget;
|
||||
pfl->dtor = freeArray;
|
||||
pfl->u.r.pvt = pvt;
|
||||
@@ -56,34 +162,246 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
return pfl;
|
||||
}
|
||||
|
||||
static void channelRegisterPre(dbChannel *chan, void *pvt,
|
||||
chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
|
||||
{
|
||||
static db_field_log *replace_fl_value(tsPrivate const *pvt,
|
||||
db_field_log *pfl,
|
||||
int (*func)(tsPrivate const *,
|
||||
db_field_log *)) {
|
||||
/* Get rid of the old value */
|
||||
if (pfl->type == dbfl_type_ref && pfl->dtor) {
|
||||
pfl->dtor(pfl);
|
||||
pfl->dtor = NULL;
|
||||
}
|
||||
pfl->no_elements = 1;
|
||||
pfl->type = dbfl_type_val;
|
||||
|
||||
if (func(pvt, pfl)) {
|
||||
db_delete_field_log(pfl);
|
||||
pfl = NULL;
|
||||
}
|
||||
|
||||
return pfl;
|
||||
}
|
||||
|
||||
static void ts_to_array(tsPrivate const *settings,
|
||||
epicsTimeStamp const *ts,
|
||||
epicsUInt32 arr[2]) {
|
||||
arr[0] = ts->secPastEpoch;
|
||||
arr[1] = ts->nsec;
|
||||
if (settings->epoch == tsEpochUnix) {
|
||||
/* Cannot use epicsTimeToWhatever because Whatever uses signed ints */
|
||||
arr[0] += POSIX_TIME_AT_EPICS_EPOCH;
|
||||
}
|
||||
}
|
||||
|
||||
static int ts_seconds(tsPrivate const *settings, db_field_log *pfl) {
|
||||
epicsUInt32 arr[2];
|
||||
ts_to_array(settings, &pfl->time, arr);
|
||||
pfl->field_type = DBF_ULONG;
|
||||
pfl->field_size = sizeof(epicsUInt32);
|
||||
pfl->u.v.field.dbf_ulong = arr[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_nanos(tsPrivate const *settings, db_field_log *pfl) {
|
||||
epicsUInt32 arr[2];
|
||||
ts_to_array(settings, &pfl->time, arr);
|
||||
pfl->field_type = DBF_ULONG;
|
||||
pfl->field_size = sizeof(epicsUInt32);
|
||||
pfl->u.v.field.dbf_ulong = arr[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_double(tsPrivate const *settings, db_field_log *pfl) {
|
||||
epicsUInt32 arr[2];
|
||||
ts_to_array(settings, &pfl->time, arr);
|
||||
pfl->field_type = DBF_DOUBLE;
|
||||
pfl->field_size = sizeof(epicsFloat64);
|
||||
pfl->u.v.field.dbf_double = arr[0] + arr[1] * 1e-9;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_array(tsPrivate const *settings, db_field_log *pfl) {
|
||||
pfl->field_type = DBF_ULONG;
|
||||
pfl->field_size = sizeof(epicsUInt32);
|
||||
pfl->type = dbfl_type_ref;
|
||||
pfl->u.r.pvt = NULL;
|
||||
pfl->u.r.field = allocTsArray();
|
||||
if (pfl->u.r.field) {
|
||||
pfl->no_elements = 2;
|
||||
pfl->dtor = freeTsArray;
|
||||
ts_to_array(settings, &pfl->time, (epicsUInt32*)pfl->u.r.field);
|
||||
} else {
|
||||
pfl->no_elements = 0;
|
||||
pfl->dtor = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts_string(tsPrivate const *settings, db_field_log *pfl) {
|
||||
char const *fmt;
|
||||
char *field;
|
||||
size_t n;
|
||||
|
||||
switch (settings->str) {
|
||||
case tsStringEpics:
|
||||
fmt = "%Y-%m-%d %H:%M:%S.%06f";
|
||||
break;
|
||||
case tsStringIso:
|
||||
fmt = "%Y-%m-%dT%H:%M:%S.%06f%z";
|
||||
break;
|
||||
case tsStringInvalid:
|
||||
default:
|
||||
logicErrorMessage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
pfl->field_type = DBF_STRING;
|
||||
pfl->field_size = MAX_STRING_SIZE;
|
||||
pfl->type = dbfl_type_ref;
|
||||
pfl->u.r.pvt = NULL;
|
||||
pfl->u.r.field = allocString();
|
||||
|
||||
if (!pfl->u.r.field) {
|
||||
pfl->no_elements = 0;
|
||||
pfl->dtor = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
pfl->dtor = freeString;
|
||||
|
||||
field = (char *)pfl->u.r.field;
|
||||
n = epicsTimeToStrftime(field, MAX_STRING_SIZE, fmt, &pfl->time);
|
||||
if (!n) {
|
||||
field[0] = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static db_field_log *filter(void *pvt, dbChannel *chan, db_field_log *pfl) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
|
||||
switch (settings->mode) {
|
||||
case tsModeDouble:
|
||||
return replace_fl_value(pvt, pfl, ts_double);
|
||||
case tsModeSec:
|
||||
return replace_fl_value(pvt, pfl, ts_seconds);
|
||||
case tsModeNsec:
|
||||
return replace_fl_value(pvt, pfl, ts_nanos);
|
||||
case tsModeArray:
|
||||
return replace_fl_value(pvt, pfl, ts_array);
|
||||
case tsModeString:
|
||||
return replace_fl_value(pvt, pfl, ts_string);
|
||||
case tsModeGenerate:
|
||||
case tsModeInvalid:
|
||||
default:
|
||||
logicErrorMessage();
|
||||
db_delete_field_log(pfl);
|
||||
pfl = NULL;
|
||||
}
|
||||
|
||||
return pfl;
|
||||
}
|
||||
|
||||
|
||||
/* Only the "generate" mode is registered for the pre-queue chain as it creates
|
||||
it's own timestamp which should be as close to the event as possible */
|
||||
static void channelRegisterPre(dbChannel * chan, void *pvt,
|
||||
chPostEventFunc **cb_out, void **arg_out,
|
||||
db_field_log *probe) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
(void)arg_out;
|
||||
(void)probe;
|
||||
|
||||
*cb_out = settings->mode == tsModeGenerate ? generate : NULL;
|
||||
}
|
||||
|
||||
/* For other modes, the post-chain is fine as they only manipulate existing
|
||||
timestamps */
|
||||
static void channelRegisterPost(dbChannel *chan, void *pvt,
|
||||
chPostEventFunc **cb_out, void **arg_out,
|
||||
db_field_log *probe) {
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
|
||||
if (settings->mode == tsModeGenerate || settings->mode == tsModeInvalid) {
|
||||
*cb_out = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
*cb_out = filter;
|
||||
*arg_out = pvt;
|
||||
|
||||
/* Get rid of the value of the probe because we will be changing the
|
||||
datatype */
|
||||
if (probe->type == dbfl_type_ref && probe->dtor) {
|
||||
probe->dtor(probe);
|
||||
probe->dtor = NULL;
|
||||
}
|
||||
probe->no_elements = 1;
|
||||
probe->type = dbfl_type_val;
|
||||
|
||||
switch (settings->mode) {
|
||||
case tsModeArray:
|
||||
probe->no_elements = 2;
|
||||
/* fallthrough */
|
||||
case tsModeSec:
|
||||
case tsModeNsec:
|
||||
probe->field_type = DBF_ULONG;
|
||||
probe->field_size = sizeof(epicsUInt32);
|
||||
break;
|
||||
case tsModeDouble:
|
||||
probe->field_type = DBF_DOUBLE;
|
||||
probe->field_size = sizeof(epicsFloat64);
|
||||
break;
|
||||
case tsModeString:
|
||||
probe->field_type = DBF_STRING;
|
||||
probe->field_size = MAX_STRING_SIZE;
|
||||
break;
|
||||
case tsModeGenerate:
|
||||
case tsModeInvalid:
|
||||
// Already handled above, added here for completeness.
|
||||
default:
|
||||
logicErrorMessage();
|
||||
*cb_out = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent)
|
||||
{
|
||||
printf("%*sTimestamp (ts)\n", indent, "");
|
||||
tsPrivate *settings = (tsPrivate *)pvt;
|
||||
(void)chan;
|
||||
(void)level;
|
||||
|
||||
printf("%*sTimestamp (ts): mode: %d, epoch: %d, str: %d\n",
|
||||
indent, "", settings->mode, settings->epoch, settings->str);
|
||||
}
|
||||
|
||||
static chfPluginIf pif = {
|
||||
NULL, /* allocPvt, */
|
||||
NULL, /* freePvt, */
|
||||
allocPvt,
|
||||
freePvt,
|
||||
|
||||
NULL, /* parse_error, */
|
||||
NULL, /* parse_ok, */
|
||||
NULL, /* parse_error, */
|
||||
parse_finished,
|
||||
|
||||
NULL, /* channel_open, */
|
||||
channelRegisterPre,
|
||||
NULL, /* channelRegisterPost, */
|
||||
channelRegisterPost,
|
||||
channel_report,
|
||||
NULL /* channel_close */
|
||||
};
|
||||
|
||||
static void tsInitialize(void)
|
||||
{
|
||||
chfPluginRegister("ts", &pif, NULL);
|
||||
freeListInitPvt(&private_free_list, sizeof(tsPrivate),
|
||||
ALLOC_NUM_ELEMENTS);
|
||||
freeListInitPvt(&ts_array_free_list, 2 * sizeof(epicsUInt32),
|
||||
ALLOC_NUM_ELEMENTS);
|
||||
freeListInitPvt(&string_free_list, MAX_STRING_SIZE,
|
||||
ALLOC_NUM_ELEMENTS);
|
||||
chfPluginRegister("ts", &pif, ts_args);
|
||||
}
|
||||
|
||||
epicsExportRegistrar(tsInitialize);
|
||||
|
||||
@@ -42,6 +42,8 @@ value is non-zero.
|
||||
|
||||
=back
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.14.10 .
|
||||
|
||||
=head2 Record-specific Menus
|
||||
|
||||
=head3 Menu aSubLFLG
|
||||
|
||||
@@ -460,12 +460,17 @@ conditions.
|
||||
|
||||
The HYST field defines an alarm deadband for each limit.
|
||||
|
||||
The AFTC field sets the time constant on a low-pass filter that delays the
|
||||
reporting of limit alarms until the signal has been within the alarm range for
|
||||
that number of seconds (the default AFTC value of zero retains the previous
|
||||
behavior).
|
||||
|
||||
See L<Alarm Specification|https://docs.epics-controls.org/en/latest/guides/EPICS_Process_Database_Concepts.html#alarm-specification>
|
||||
for a complete explanation of record alarms and of the standard fields.
|
||||
L<Alarm Fields|dbCommonRecord/Alarm Fields> lists other fields related
|
||||
to alarms that are common to all record types.
|
||||
|
||||
=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST
|
||||
=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, AFTC
|
||||
|
||||
=head3 Monitor Parameters
|
||||
|
||||
@@ -496,7 +501,7 @@ manner for the VAL field.
|
||||
|
||||
=cut
|
||||
|
||||
include "dbCommon.dbd"
|
||||
include "dbCommon.dbd"
|
||||
field(VAL,DBF_DOUBLE) {
|
||||
prompt("Result")
|
||||
promptgroup("50 - Output")
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "dbEvent.h"
|
||||
#include "dbFldTypes.h"
|
||||
#include "errMdef.h"
|
||||
#include "menuYesNo.h"
|
||||
#include "special.h"
|
||||
#include "recSup.h"
|
||||
#include "recGbl.h"
|
||||
@@ -166,9 +167,9 @@ static int compress_array(compressRecord *prec,
|
||||
}
|
||||
if (prec->n <= 0)
|
||||
prec->n = 1;
|
||||
n = prec->n;
|
||||
if (no_elements < n)
|
||||
if (no_elements < prec->n && prec->pbuf != menuYesNoYES)
|
||||
return 1; /*dont do anything*/
|
||||
n = no_elements;
|
||||
|
||||
/* determine number of samples to take */
|
||||
if (no_elements < nsam * n)
|
||||
@@ -272,7 +273,7 @@ static int array_average(compressRecord *prec,
|
||||
prec->inx = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int compress_scalar(struct compressRecord *prec,double *psource)
|
||||
{
|
||||
double value = *psource;
|
||||
@@ -292,19 +293,13 @@ static int compress_scalar(struct compressRecord *prec,double *psource)
|
||||
/* for scalars, Median not implemented => use average */
|
||||
case (compressALG_N_to_1_Average):
|
||||
case (compressALG_N_to_1_Median):
|
||||
if (inx == 0)
|
||||
*pdest = value;
|
||||
else {
|
||||
*pdest += value;
|
||||
if (inx + 1 >= prec->n)
|
||||
*pdest = *pdest / (inx + 1);
|
||||
}
|
||||
*pdest = (inx * (*pdest) + value) / (inx + 1);
|
||||
break;
|
||||
}
|
||||
inx++;
|
||||
if (inx >= prec->n) {
|
||||
if ((inx >= prec->n) || (prec->pbuf == menuYesNoYES)) {
|
||||
put_value(prec,pdest,1);
|
||||
prec->inx = 0;
|
||||
prec->inx = (inx >= prec->n) ? 0 : inx;
|
||||
return 0;
|
||||
} else {
|
||||
prec->inx = inx;
|
||||
|
||||
@@ -40,7 +40,7 @@ the beginning or the end of the VAL array.
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
=recordtype compress
|
||||
|
||||
@@ -60,10 +60,6 @@ menu(bufferingALG) {
|
||||
}
|
||||
recordtype(compress) {
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
=head3 Scanning Parameters
|
||||
|
||||
The compression record has the standard fields for specifying under what
|
||||
@@ -85,7 +81,7 @@ algorithms which can be specified as follows:
|
||||
|
||||
The following fields determine what channel to read and how to compress the data:
|
||||
|
||||
=fields ALG, INP, NSAM, N, ILIL, IHIL, OFF, RES
|
||||
=fields ALG, INP, NSAM, N, ILIL, IHIL, OFF, RES, PBUF
|
||||
|
||||
As stated above, the ALG field specifies which algorithm to be performed on the data.
|
||||
|
||||
@@ -167,6 +163,23 @@ Compress N to 1 samples, taking the median value.
|
||||
|
||||
=back
|
||||
|
||||
The behaviour of the record for partially filled buffers depends on the field PBUF.
|
||||
If PBUF is set to NO, then the record will wait until the buffer is completely full
|
||||
before processing. If PBUF is set to YES, then it will start processing immediately.
|
||||
|
||||
For example, if ALG is set to C<<< N to 1 Average >>> with NSAM equal to 4, N equal
|
||||
to 1, and PBUF set to NO, then the first three times that the compress record is
|
||||
processed it will remain in an undefined state. On the fourth process, the average
|
||||
of all four records will be calculated and placed into the VAL field.
|
||||
|
||||
If PBUF is set to YES, then after each process the average of the first several
|
||||
elements will be calculated.
|
||||
|
||||
Note that PBUF has no impact on the C<<< Average >>> method. If one wishes to have a
|
||||
rolling average computed, then the best way to achieve that is with two compress
|
||||
records: a C<<< Circular buffer >>> which is linked to an C<<< N to 1 Average >>>
|
||||
record with PBUF set to YES.
|
||||
|
||||
The compression record keeps NSAM data samples.
|
||||
|
||||
The field N determines the number of elements to compress into each result.
|
||||
@@ -393,7 +406,15 @@ Scan forward link if necessary, set PACT FALSE, and return.
|
||||
interest(1)
|
||||
menu(compressALG)
|
||||
}
|
||||
field(BALG,DBF_MENU) {
|
||||
field(PBUF,DBF_MENU) {
|
||||
prompt("Use Partial buffers")
|
||||
promptgroup("30 - Action")
|
||||
special(SPC_RESET)
|
||||
interest(1)
|
||||
menu(menuYesNo)
|
||||
initial("NO")
|
||||
}
|
||||
field(BALG,DBF_MENU) {
|
||||
prompt("Buffering Algorithm")
|
||||
promptgroup("30 - Action")
|
||||
special(SPC_RESET)
|
||||
|
||||
@@ -13,6 +13,8 @@ The histogram record is used to store frequency counts of a signal into an array
|
||||
of arbitrary length. The user can configure the range of the signal value that
|
||||
the array will store. Anything outside this range will be ignored.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.1 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
|
||||
@@ -14,6 +14,8 @@ from a hardware input.
|
||||
The record supports alarm limits, alarm filtering, graphics and control
|
||||
limits.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.16.1 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
|
||||
@@ -13,6 +13,8 @@ This record type is normally used to send an integer value of up to 64 bits
|
||||
to an output device.
|
||||
The record supports alarm, drive, graphics and control limits.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.16.1 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below.
|
||||
|
||||
@@ -70,6 +70,15 @@ The possible alarm conditions for long inputs are the SCAN, READ, and limit
|
||||
alarms. The SCAN and READ alarms are called by the record or device support
|
||||
routines.
|
||||
|
||||
The HYST field controls hysteresis to prevent alarm chattering from an input
|
||||
signal that is close to one of the limits and suffers from significant readout
|
||||
noise.
|
||||
|
||||
The AFTC field sets the time constant on a low-pass filter that delays the
|
||||
reporting of limit alarms until the signal has been within the alarm range for
|
||||
that number of seconds (the default AFTC value of zero retains the previous
|
||||
behavior).
|
||||
|
||||
The limit alarms are configured by the user in the HIHI, LOLO, HIGH, and LOW
|
||||
fields using numerical values. For each of these fields, there is a
|
||||
corresponding severity field which can be either NO_ALARM, MINOR, or MAJOR. The
|
||||
@@ -77,7 +86,7 @@ HYST field can be used to specify a deadband around each limit.
|
||||
L<Alarm Fields|dbCommonRecord/Alarm Fields> lists the fields related to
|
||||
alarms that are common to all record types.
|
||||
|
||||
=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST
|
||||
=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, AFTC
|
||||
|
||||
=head3 Monitor Parameters
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
The long string input record is used to retrieve an arbitrary ASCII string with
|
||||
a maximum length of 65535 characters.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
The long string output record is used to write an arbitrary ASCII string with a
|
||||
maximum length of 65535 characters.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
@@ -405,6 +405,11 @@ and state alarms. The state alarms are configured in the below severity fields.
|
||||
These fields have the usual possible values for severity fields: NO_ALARM,
|
||||
MINOR, and MAJOR.
|
||||
|
||||
The AFTC field sets the time constant on a low-pass filter that delays the
|
||||
reporting of limit alarms until the signal has been within the alarm range for
|
||||
that number of seconds (the default AFTC value of zero retains the previous
|
||||
behavior).
|
||||
|
||||
The unknown state severity (UNSV) field, if set to MINOR or MAJOR, triggers an
|
||||
alarm when the record support routine cannot find a matching value in the state
|
||||
value fields for C<<< rval >>>.
|
||||
@@ -420,7 +425,7 @@ for a complete explanation of record alarms and of the standard fields.
|
||||
L<Alarm Fields|dbCommonRecord/Alarm Fields> lists other fields related
|
||||
to alarms that are common to all record types.
|
||||
|
||||
=fields UNSV, COSV, ZRSV, ONSV, TWSV, THSV, FRSV, FVSV, SXSV, SVSV, EISV, NISV, TESV, ELSV, TVSV, TTSV, FTSV, FFSV
|
||||
=fields UNSV, COSV, ZRSV, ONSV, TWSV, THSV, FRSV, FVSV, SXSV, SVSV, EISV, NISV, TESV, ELSV, TVSV, TTSV, FTSV, FFSV, AFTC
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ static long special(DBADDR *paddr, int after)
|
||||
} else if(after==1 && fieldIndex >= mbboDirectRecordB0 && fieldIndex <= mbboDirectRecordB1F) {
|
||||
/* Adjust VAL corresponding to the bit changed */
|
||||
epicsUInt8 *pBn = (epicsUInt8 *) paddr->pfield;
|
||||
epicsUInt32 bit = 1 << (pBn - &prec->b0);
|
||||
epicsUInt32 bit = 1u << (pBn - &prec->b0);
|
||||
|
||||
/* Because this is !(VAL and PP), dbPut() will always post a monitor on this B* field
|
||||
* after we return. We must keep track of this change separately from MLST to handle
|
||||
|
||||
@@ -169,7 +169,7 @@ static void doPrintf(printfRecord *prec)
|
||||
precision = 0;
|
||||
|
||||
if (ch == '%') {
|
||||
added = epicsSnprintf(pval, vspace + 1, "%s", format);
|
||||
added = epicsSnprintf(pval, vspace + 1, format);
|
||||
}
|
||||
else if (linkn++ >= PRINTF_NLINKS) {
|
||||
/* No more LNKn fields */
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
The printf record is used to generate and write a string using a format
|
||||
specification and parameters, analogous to the C C<printf()> function.
|
||||
|
||||
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
|
||||
|
||||
=head2 Parameter Fields
|
||||
|
||||
The record-specific fields are described below, grouped by functionality.
|
||||
|
||||
@@ -162,7 +162,7 @@ static long process(struct dbCommon *pcommon)
|
||||
recGblFwdLink(prec);
|
||||
prec->pact = FALSE;
|
||||
|
||||
return 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
static long special(DBADDR *paddr, int after)
|
||||
|
||||
@@ -3,7 +3,7 @@ To start the ioc from this directory execute the command
|
||||
|
||||
Alternatively make the st.cmd file directly executable with
|
||||
chmod +x st.cmd
|
||||
and check the executable name on the first line of the st.cmd file
|
||||
and check the executable name on the first line of the st.cmd file
|
||||
|
||||
You may need to change the name of the .dbd file given in the
|
||||
st.cmd's dbLoadDatabase() command before starting the ioc.
|
||||
|
||||
@@ -146,7 +146,7 @@ __EOF__
|
||||
" prt->papFldDes[${rn}Record${fn}]->size = " .
|
||||
"sizeof(prec->${cn});\n" .
|
||||
" prt->papFldDes[${rn}Record${fn}]->offset = " .
|
||||
"(unsigned short)((char *)&prec->${cn} - (char *)prec);"
|
||||
"(unsigned short)offsetof(${rn}Record, ${cn});"
|
||||
} @fields), << "__EOF__";
|
||||
|
||||
prt->rec_size = sizeof(*prec);
|
||||
|
||||
@@ -181,6 +181,8 @@ dbStaticTest_SRCS += dbStaticTest.c
|
||||
dbStaticTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += dbStaticTest.c
|
||||
TESTFILES += ../dbStaticTest.db
|
||||
TESTFILES += ../dbStaticTestAlias1.db
|
||||
TESTFILES += ../dbStaticTestAlias2.db
|
||||
TESTS += dbStaticTest
|
||||
|
||||
# This runs all the test programs in a known working order:
|
||||
|
||||
@@ -291,6 +291,18 @@ static void testDbVerify(const char *record)
|
||||
dbFinishEntry(&entry);
|
||||
}
|
||||
|
||||
static void testWrongAliasRecord(const char *filename)
|
||||
{
|
||||
FILE *fp = NULL;
|
||||
dbPath(pdbbase,"." OSI_PATH_LIST_SEPARATOR "..");
|
||||
dbOpenFile(pdbbase, filename, &fp);
|
||||
if(!fp) {
|
||||
testAbort("Unable to read %s", filename);
|
||||
}
|
||||
testOk(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL) != 0,
|
||||
"Wrong alias record in %s is expected to fail", filename);
|
||||
}
|
||||
|
||||
void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
MAIN(dbStaticTest)
|
||||
@@ -298,13 +310,14 @@ MAIN(dbStaticTest)
|
||||
const char *ldir;
|
||||
FILE *fp = NULL;
|
||||
|
||||
testPlan(310);
|
||||
testPlan(312);
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
|
||||
dbTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
dbPath(pdbbase,"." OSI_PATH_LIST_SEPARATOR "..");
|
||||
if(!(ldir = dbOpenFile(pdbbase, "dbStaticTest.db", &fp))) {
|
||||
ldir = dbOpenFile(pdbbase, "dbStaticTest.db", &fp);
|
||||
if(!fp) {
|
||||
testAbort("Unable to read dbStaticTest.db");
|
||||
}
|
||||
if(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL)) {
|
||||
@@ -312,6 +325,9 @@ MAIN(dbStaticTest)
|
||||
ldir, OSI_PATH_LIST_SEPARATOR);
|
||||
}
|
||||
|
||||
testWrongAliasRecord("dbStaticTestAlias1.db");
|
||||
testWrongAliasRecord("dbStaticTestAlias2.db");
|
||||
|
||||
testEntry("testrec.VAL");
|
||||
testEntry("testalias.VAL");
|
||||
testEntry("testalias2.VAL");
|
||||
|
||||
4
modules/database/test/ioc/db/dbStaticTestAlias1.db
Normal file
4
modules/database/test/ioc/db/dbStaticTestAlias1.db
Normal file
@@ -0,0 +1,4 @@
|
||||
record(x, "testrec2alias") {
|
||||
}
|
||||
|
||||
alias("testrec2alias.NAME", "testalias4")
|
||||
4
modules/database/test/ioc/db/dbStaticTestAlias2.db
Normal file
4
modules/database/test/ioc/db/dbStaticTestAlias2.db
Normal file
@@ -0,0 +1,4 @@
|
||||
record(x, "testrec2alias2") {
|
||||
}
|
||||
|
||||
alias("testrec2alias2", "testalias5.NAME")
|
||||
@@ -1,4 +1,5 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2021 Cosylab d.d
|
||||
* Copyright (c) 2010 Brookhaven National Laboratory.
|
||||
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
|
||||
* fuer Materialien und Energie GmbH.
|
||||
@@ -9,9 +10,11 @@
|
||||
|
||||
/*
|
||||
* Author: Ralph Lange <Ralph.Lange@bessy.de>
|
||||
* Author: Jure Varlec <jure.varlec@cosylab.com>
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "dbStaticLib.h"
|
||||
#include "dbAccessDefs.h"
|
||||
@@ -25,11 +28,24 @@
|
||||
#include "testMain.h"
|
||||
#include "osiFileName.h"
|
||||
|
||||
/* A fill pattern for setting a field log to something "random". */
|
||||
#define PATTERN 0x55
|
||||
|
||||
void filterTest_registerRecordDeviceDriver(struct dbBase *);
|
||||
/* Use a "normal" timestamp for testing. What results from filling the field log
|
||||
with the above pattern is a timestamp that causes problems with
|
||||
epicsTimeToStrftime() on some platforms. */
|
||||
static epicsTimeStamp const test_ts = { 616600420, 998425354 };
|
||||
|
||||
static db_field_log fl;
|
||||
typedef int (*TypeCheck)(const db_field_log *pfl);
|
||||
typedef int (*ValueCheck)(const db_field_log *pfl, const epicsTimeStamp *ts);
|
||||
|
||||
typedef struct {
|
||||
char const *channel;
|
||||
TypeCheck type_check;
|
||||
ValueCheck value_check;
|
||||
} TestSpec;
|
||||
|
||||
void filterTest_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) {
|
||||
return !(memcmp(pfl1, pfl2, sizeof(db_field_log)));
|
||||
@@ -42,21 +58,210 @@ static int fl_equal_ex_ts(const db_field_log *pfl1, const db_field_log *pfl2) {
|
||||
return fl_equal(&fl1, pfl2);
|
||||
}
|
||||
|
||||
MAIN(tsTest)
|
||||
{
|
||||
static void fl_reset(db_field_log *pfl) {
|
||||
memset(pfl, PATTERN, sizeof(*pfl));
|
||||
pfl->time = test_ts;
|
||||
}
|
||||
|
||||
static void test_generate_filter(const chFilterPlugin *plug) {
|
||||
dbChannel *pch;
|
||||
chFilter *filter;
|
||||
const chFilterPlugin *plug;
|
||||
char ts[] = "ts";
|
||||
ELLNODE *node;
|
||||
chPostEventFunc *cb_out = NULL;
|
||||
void *arg_out = NULL;
|
||||
db_field_log fl;
|
||||
db_field_log fl1;
|
||||
db_field_log *pfl2;
|
||||
epicsTimeStamp stamp, now;
|
||||
dbEventCtx evtctx;
|
||||
ELLNODE *node;
|
||||
chPostEventFunc *cb_out = NULL;
|
||||
void *arg_out = NULL;
|
||||
char const *chan_name = "x.VAL{ts:{}}";
|
||||
|
||||
testPlan(12);
|
||||
testOk(!!(pch = dbChannelCreate(chan_name)),
|
||||
"dbChannel with plugin ts created");
|
||||
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl1 = fl;
|
||||
node = ellFirst(&pch->filters);
|
||||
filter = CONTAINER(node, chFilter, list_node);
|
||||
plug->fif->channel_register_post(filter, &cb_out, &arg_out, &fl1);
|
||||
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1);
|
||||
testOk(!!(cb_out) && !(arg_out),
|
||||
"register_pre registers one filter w/o argument");
|
||||
testOk(fl_equal(&fl1, &fl),
|
||||
"register_pre does not change field_log data type");
|
||||
|
||||
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
|
||||
node = ellFirst(&pch->pre_chain);
|
||||
filter = CONTAINER(node, chFilter, pre_node);
|
||||
testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL),
|
||||
"ts has one filter w/o argument in pre chain");
|
||||
testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl1 = fl;
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log");
|
||||
testOk(fl_equal_ex_ts(&fl1, pfl2),
|
||||
"ts filter does not change field_log data");
|
||||
|
||||
testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel");
|
||||
stamp = pfl2->time;
|
||||
db_delete_field_log(pfl2);
|
||||
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
epicsTimeGetCurrent(&now);
|
||||
testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 &&
|
||||
epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0,
|
||||
"ts filter sets time stamp to \"now\"");
|
||||
|
||||
dbChannelDelete(pch);
|
||||
}
|
||||
|
||||
static void test_value_filter(const chFilterPlugin *plug, const char *chan_name,
|
||||
TypeCheck tc_func, ValueCheck vc_func) {
|
||||
dbChannel *pch;
|
||||
chFilter *filter;
|
||||
db_field_log fl;
|
||||
db_field_log fl2;
|
||||
db_field_log *pfl;
|
||||
epicsTimeStamp ts;
|
||||
ELLNODE *node;
|
||||
chPostEventFunc *cb_out = NULL;
|
||||
void *arg_out = NULL;
|
||||
|
||||
testDiag("Channel %s", chan_name);
|
||||
|
||||
testOk(!!(pch = dbChannelCreate(chan_name)),
|
||||
"dbChannel with plugin ts created");
|
||||
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl.type = dbfl_type_val;
|
||||
node = ellFirst(&pch->filters);
|
||||
filter = CONTAINER(node, chFilter, list_node);
|
||||
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl);
|
||||
plug->fif->channel_register_post(filter, &cb_out, &arg_out, &fl);
|
||||
testOk(!!(cb_out) && arg_out,
|
||||
"register_post registers one filter with argument");
|
||||
testOk(tc_func(&fl), "register_post gives correct field type");
|
||||
|
||||
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
|
||||
node = ellFirst(&pch->post_chain);
|
||||
filter = CONTAINER(node, chFilter, post_node);
|
||||
testOk((ellCount(&pch->post_chain) == 1 && filter->post_arg != NULL),
|
||||
"ts has one filter with argument in post chain");
|
||||
testOk((ellCount(&pch->pre_chain) == 0), "ts has no filter in pre chain");
|
||||
|
||||
fl_reset(&fl);
|
||||
fl.type = dbfl_type_val;
|
||||
ts = fl.time;
|
||||
fl2 = fl;
|
||||
pfl = dbChannelRunPostChain(pch, &fl);
|
||||
testOk(pfl == &fl, "ts filter does not drop or replace field_log");
|
||||
testOk(tc_func(pfl), "ts filter gives correct field type");
|
||||
testOk((pfl->time.secPastEpoch == fl2.time.secPastEpoch &&
|
||||
pfl->time.nsec == fl2.time.nsec && pfl->stat == fl2.stat &&
|
||||
pfl->sevr == fl2.sevr),
|
||||
"ts filter does not touch non-value fields of field_log");
|
||||
testOk(vc_func(pfl, &ts), "ts filter gives correct field value");
|
||||
|
||||
dbChannelDelete(pch);
|
||||
}
|
||||
|
||||
static int type_check_double(const db_field_log *pfl) {
|
||||
return pfl->type == dbfl_type_val
|
||||
&& pfl->field_type == DBR_DOUBLE
|
||||
&& pfl->field_size == sizeof(epicsFloat64)
|
||||
&& pfl->no_elements == 1;
|
||||
}
|
||||
|
||||
static int value_check_double(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
epicsFloat64 flt = pfl->u.v.field.dbf_double;
|
||||
epicsFloat64 nsec = (flt - (epicsUInt32)(flt)) * 1e9;
|
||||
return ts->secPastEpoch == (epicsUInt32)(flt)
|
||||
&& fabs(ts->nsec - nsec) < 1000.; /* allow loss of precision */
|
||||
}
|
||||
|
||||
static int type_check_sec_nsec(const db_field_log *pfl) {
|
||||
return pfl->type == dbfl_type_val
|
||||
&& pfl->field_type == DBR_ULONG
|
||||
&& pfl->field_size == sizeof(epicsUInt32)
|
||||
&& pfl->no_elements == 1;
|
||||
}
|
||||
|
||||
static int value_check_sec(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
return ts->secPastEpoch == pfl->u.v.field.dbf_ulong;
|
||||
}
|
||||
|
||||
static int value_check_nsec(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
return ts->nsec == pfl->u.v.field.dbf_ulong;
|
||||
}
|
||||
|
||||
static int type_check_array(const db_field_log *pfl) {
|
||||
return pfl->field_type == DBR_ULONG
|
||||
&& pfl->field_size == sizeof(epicsUInt32)
|
||||
&& pfl->no_elements == 2;
|
||||
}
|
||||
|
||||
static int value_check_array(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
epicsUInt32 *arr = (epicsUInt32*)pfl->u.r.field;
|
||||
return pfl->type == dbfl_type_ref
|
||||
&& pfl->u.r.field != NULL
|
||||
&& pfl->dtor != NULL
|
||||
&& pfl->u.r.pvt == NULL
|
||||
&& ts->secPastEpoch == arr[0]
|
||||
&& ts->nsec == arr[1];
|
||||
}
|
||||
|
||||
static int value_check_unix(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
epicsUInt32 *arr = (epicsUInt32 *)pfl->u.r.field;
|
||||
return pfl->type == dbfl_type_ref
|
||||
&& pfl->u.r.field != NULL
|
||||
&& pfl->dtor != NULL
|
||||
&& pfl->u.r.pvt == NULL
|
||||
&& ts->secPastEpoch == arr[0] - POSIX_TIME_AT_EPICS_EPOCH
|
||||
&& ts->nsec == arr[1];
|
||||
}
|
||||
|
||||
static int type_check_string(const db_field_log *pfl) {
|
||||
return pfl->field_type == DBR_STRING
|
||||
&& pfl->field_size == MAX_STRING_SIZE
|
||||
&& pfl->no_elements == 1;
|
||||
}
|
||||
|
||||
static int value_check_string(const db_field_log *pfl, const epicsTimeStamp *ts) {
|
||||
/* We can only verify the type, not the value, because using strptime()
|
||||
might be problematic. */
|
||||
(void)ts;
|
||||
return pfl->type == dbfl_type_ref
|
||||
&& pfl->u.r.field != NULL
|
||||
&& pfl->dtor != NULL
|
||||
&& pfl->u.r.pvt == NULL;
|
||||
}
|
||||
|
||||
MAIN(tsTest) {
|
||||
int i;
|
||||
char ts[] = "ts";
|
||||
dbEventCtx evtctx;
|
||||
const chFilterPlugin *plug;
|
||||
|
||||
static TestSpec const tests[] = {
|
||||
{"x.VAL{ts:{\"num\": \"dbl\"}}", type_check_double, value_check_double},
|
||||
{"x.VAL{ts:{\"num\": \"sec\"}}", type_check_sec_nsec, value_check_sec},
|
||||
{"x.VAL{ts:{\"num\": \"nsec\"}}", type_check_sec_nsec, value_check_nsec},
|
||||
{"x.VAL{ts:{\"num\": \"ts\"}}", type_check_array, value_check_array},
|
||||
{"x.VAL{ts:{\"num\": \"ts\", \"epoch\": \"epics\"}}", type_check_array, value_check_array},
|
||||
{"x.VAL{ts:{\"num\": \"ts\", \"epoch\": \"unix\"}}", type_check_array, value_check_unix},
|
||||
{"x.VAL{ts:{\"str\": \"epics\"}}", type_check_string, value_check_string},
|
||||
#if !(defined _MSC_VER && _MSC_VER <= 1700)
|
||||
{"x.VAL{ts:{\"str\": \"iso\"}}", type_check_string, value_check_string},
|
||||
#endif
|
||||
};
|
||||
|
||||
static int const num_value_tests = sizeof(tests) / sizeof(tests[0]);
|
||||
|
||||
testPlan(12 /* test_generate_filter() */
|
||||
+ num_value_tests * 11 /* test_value_filter() */);
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
@@ -77,41 +282,12 @@ MAIN(tsTest)
|
||||
testAbort("plugin '%s' not registered", ts);
|
||||
testPass("plugin '%s' registered correctly", ts);
|
||||
|
||||
testOk(!!(pch = dbChannelCreate("x.VAL{ts:{}}")), "dbChannel with plugin ts created");
|
||||
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
|
||||
test_generate_filter(plug);
|
||||
|
||||
memset(&fl, PATTERN, sizeof(fl));
|
||||
fl1 = fl;
|
||||
node = ellFirst(&pch->filters);
|
||||
filter = CONTAINER(node, chFilter, list_node);
|
||||
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1);
|
||||
testOk(!!(cb_out) && !(arg_out), "register_pre registers one filter w/o argument");
|
||||
testOk(fl_equal(&fl1, &fl), "register_pre does not change field_log data type");
|
||||
|
||||
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
|
||||
node = ellFirst(&pch->pre_chain);
|
||||
filter = CONTAINER(node, chFilter, pre_node);
|
||||
testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL),
|
||||
"ts has one filter w/o argument in pre chain");
|
||||
testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain");
|
||||
|
||||
memset(&fl, PATTERN, sizeof(fl));
|
||||
fl1 = fl;
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log");
|
||||
testOk(fl_equal_ex_ts(&fl1, pfl2), "ts filter does not change field_log data");
|
||||
|
||||
testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel");
|
||||
stamp = pfl2->time;
|
||||
db_delete_field_log(pfl2);
|
||||
|
||||
pfl2 = dbChannelRunPreChain(pch, &fl1);
|
||||
epicsTimeGetCurrent(&now);
|
||||
testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 &&
|
||||
epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0,
|
||||
"ts filter sets time stamp to \"now\"");
|
||||
|
||||
dbChannelDelete(pch);
|
||||
for (i = 0; i < num_value_tests; ++i) {
|
||||
TestSpec const *t = &tests[i];
|
||||
test_value_filter(plug, t->channel, t->type_check, t->value_check);
|
||||
}
|
||||
|
||||
db_close_events(evtctx);
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ PROD_LIBS = dbRecStdTest dbRecStd dbCore ca Com
|
||||
TARGETS += $(COMMON_DIR)/recTestIoc.dbd
|
||||
DBDDEPENDS_FILES += recTestIoc.dbd$(DEP)
|
||||
recTestIoc_DBD = base.dbd
|
||||
recTestIoc_DBD += bptTypeKdegC.dbd
|
||||
recTestIoc_DBD += bptTypeKdegF.dbd
|
||||
recTestIoc_DBD += bptTypeJdegF.dbd
|
||||
recTestIoc_DBD += bptTypeJdegC.dbd
|
||||
|
||||
TESTFILES += $(COMMON_DIR)/recTestIoc.dbd
|
||||
|
||||
testHarness_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
|
||||
@@ -93,6 +98,34 @@ testHarness_SRCS += longoutTest.c
|
||||
TESTFILES += ../longoutTest.db
|
||||
TESTS += longoutTest
|
||||
|
||||
TESTPROD_HOST += boTest
|
||||
boTest_SRCS += boTest.c
|
||||
boTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += boTest.c
|
||||
TESTFILES += ../boTest.db
|
||||
TESTS += boTest
|
||||
|
||||
TESTPROD_HOST += biTest
|
||||
biTest_SRCS += biTest.c
|
||||
biTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += biTest.c
|
||||
TESTFILES += ../biTest.db
|
||||
TESTS += biTest
|
||||
|
||||
TESTPROD_HOST += printfTest
|
||||
printfTest_SRCS += printfTest.c
|
||||
printfTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += printfTest.c
|
||||
TESTFILES += ../printfTest.db
|
||||
TESTS += printfTest
|
||||
|
||||
TESTPROD_HOST += aiTest
|
||||
aiTest_SRCS += aiTest.c
|
||||
aiTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += aiTest.c
|
||||
TESTFILES += ../aiTest.db
|
||||
TESTS += aiTest
|
||||
|
||||
TARGETS += $(COMMON_DIR)/asTestIoc.dbd
|
||||
DBDDEPENDS_FILES += asTestIoc.dbd$(DEP)
|
||||
asTestIoc_DBD += base.dbd
|
||||
@@ -167,6 +200,16 @@ asyncproctest_SRCS += asyncproctest_registerRecordDeviceDriver.cpp
|
||||
TESTFILES += $(COMMON_DIR)/asyncproctest.dbd ../asyncproctest.db
|
||||
TESTS += asyncproctest
|
||||
|
||||
TARGETS += $(COMMON_DIR)/subproctest.dbd
|
||||
DBDDEPENDS_FILES += subproctest.dbd$(DEP)
|
||||
subproctest_DBD += base.dbd
|
||||
TESTPROD_HOST += subproctest
|
||||
subproctest_SRCS += subproctest.c
|
||||
subproctest_SRCS += subproctest_registerRecordDeviceDriver.cpp
|
||||
TESTFILES += $(COMMON_DIR)/subproctest.dbd ../subproctest.db
|
||||
TESTS += subproctest
|
||||
|
||||
|
||||
TESTPROD_HOST += linkFilterTest
|
||||
linkFilterTest_SRCS += linkFilterTest.c
|
||||
linkFilterTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
|
||||
|
||||
445
modules/database/test/std/rec/aiTest.c
Normal file
445
modules/database/test/std/rec/aiTest.c
Normal file
@@ -0,0 +1,445 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2023 Karl Vestin
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#include "dbUnitTest.h"
|
||||
#include "testMain.h"
|
||||
#include "errlog.h"
|
||||
#include "dbAccess.h"
|
||||
#include "menuAlarmSevr.h"
|
||||
#include "menuConvert.h"
|
||||
#include "epicsMath.h"
|
||||
#include "menuScan.h"
|
||||
#include "caeventmask.h"
|
||||
|
||||
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
static void test_soft_input(void){
|
||||
double value = 543.123;
|
||||
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Soft Channel");
|
||||
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
|
||||
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
|
||||
|
||||
/* set VAL to on linked record */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, value);
|
||||
|
||||
/* verify that this record VAL is updated but RVAL is not */
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_DOUBLE, value);
|
||||
testdbGetFieldEqual("test_ai_rec.RVAL", DBF_DOUBLE, 0.0);
|
||||
|
||||
// number of tests = 6
|
||||
}
|
||||
|
||||
static void test_raw_soft_input(void){
|
||||
double value = -255;
|
||||
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
|
||||
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
|
||||
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
|
||||
|
||||
/* set VAL to on linked record */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, value);
|
||||
|
||||
/* verify that this record RVAL and VAL are updated */
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_DOUBLE, value);
|
||||
testdbGetFieldEqual("test_ai_rec.RVAL", DBF_DOUBLE, value);
|
||||
|
||||
// number of tests = 6
|
||||
}
|
||||
|
||||
static void test_operator_display(void){
|
||||
const short hopr = 199;
|
||||
const short lopr = 50;
|
||||
const short prec = 2;
|
||||
const char egu[] = "mm";
|
||||
const char desc[] = "hmm?";
|
||||
|
||||
/* set operator display parameters */
|
||||
testdbPutFieldOk("test_ai_rec.EGU", DBF_STRING, egu);
|
||||
testdbPutFieldOk("test_ai_rec.DESC", DBF_STRING, desc);
|
||||
testdbPutFieldOk("test_ai_rec.HOPR", DBF_SHORT, hopr);
|
||||
testdbPutFieldOk("test_ai_rec.LOPR", DBF_SHORT, lopr);
|
||||
testdbPutFieldOk("test_ai_rec.PREC", DBF_SHORT, prec);
|
||||
|
||||
/* verify operator display parameters read back */
|
||||
testdbGetFieldEqual("test_ai_rec.NAME", DBF_STRING, "test_ai_rec");
|
||||
testdbGetFieldEqual("test_ai_rec.EGU", DBF_STRING, egu);
|
||||
testdbGetFieldEqual("test_ai_rec.DESC", DBF_STRING, desc);
|
||||
testdbGetFieldEqual("test_ai_rec.HOPR", DBF_SHORT, hopr);
|
||||
testdbGetFieldEqual("test_ai_rec.LOPR", DBF_SHORT, lopr);
|
||||
testdbGetFieldEqual("test_ai_rec.PREC", DBF_SHORT, prec);
|
||||
|
||||
// number of tests = 7
|
||||
}
|
||||
|
||||
static void test_no_linr_unit_conversion(void){
|
||||
const short roff = 10;
|
||||
const short aslo = 2;
|
||||
const short aoff = 4;
|
||||
|
||||
const short rval = 9;
|
||||
const short val = (((rval + roff) * aslo) + aoff);
|
||||
|
||||
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
|
||||
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
|
||||
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
|
||||
|
||||
/* set unit conversion parameters */
|
||||
testdbPutFieldOk("test_ai_rec.ROFF", DBF_SHORT, roff);
|
||||
testdbPutFieldOk("test_ai_rec.ASLO", DBF_SHORT, aslo);
|
||||
testdbPutFieldOk("test_ai_rec.AOFF", DBF_SHORT, aoff);
|
||||
testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertNO_CONVERSION);
|
||||
|
||||
/* verify conversion */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_SHORT, rval);
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_SHORT, val);
|
||||
testdbGetFieldEqual("test_ai_rec.RVAL", DBF_SHORT, rval);
|
||||
|
||||
// number of tests = 10
|
||||
}
|
||||
|
||||
static void test_slope_linr_unit_conversion(void){
|
||||
const short roff = 1;
|
||||
const short aslo = 3;
|
||||
const short aoff = 99;
|
||||
const short eslo = 3;
|
||||
const short eoff = 2;
|
||||
|
||||
const short rval = 7;
|
||||
const short val = ((((rval + roff) * aslo) + aoff) * eslo) + eoff;
|
||||
|
||||
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
|
||||
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
|
||||
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
|
||||
|
||||
/* set unit conversion parameters */
|
||||
testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, roff);
|
||||
testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, aslo);
|
||||
testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, aoff);
|
||||
testdbPutFieldOk("test_ai_rec.ESLO", DBF_LONG, eslo);
|
||||
testdbPutFieldOk("test_ai_rec.EOFF", DBF_LONG, eoff);
|
||||
testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertSLOPE);
|
||||
|
||||
/* verify conversion */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_LONG, rval);
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_LONG, val);
|
||||
testdbGetFieldEqual("test_ai_rec.RVAL", DBF_LONG, rval);
|
||||
|
||||
// number of tests = 12
|
||||
}
|
||||
|
||||
static void test_linear_linr_unit_conversion(void){
|
||||
const long roff = 6;
|
||||
const long aslo = 2;
|
||||
const long aoff = 19;
|
||||
const long eslo = 4;
|
||||
const long eoff = 1;
|
||||
const long egul = 9999;
|
||||
const long eguf = -1000;
|
||||
|
||||
const long rval = 2;
|
||||
/* Since our raw soft input does not support EGUL and EGUF conversion LINEAR should work like SLOPE */
|
||||
const long val = ((((rval + roff) * aslo) + aoff) * eslo) + eoff;
|
||||
|
||||
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
|
||||
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
|
||||
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
|
||||
|
||||
/* set unit conversion parameters */
|
||||
testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, roff);
|
||||
testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, aslo);
|
||||
testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, aoff);
|
||||
testdbPutFieldOk("test_ai_rec.ESLO", DBF_LONG, eslo);
|
||||
testdbPutFieldOk("test_ai_rec.EOFF", DBF_LONG, eoff);
|
||||
/* Since our raw soft input does not support EGUL and EGUF conversion we set them to lagre values here just to check they dont break anything */
|
||||
testdbPutFieldOk("test_ai_rec.EGUL", DBF_LONG, egul);
|
||||
testdbPutFieldOk("test_ai_rec.EGUF", DBF_LONG, eguf);
|
||||
testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertLINEAR);
|
||||
|
||||
/* verify conversion */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_LONG, rval);
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_LONG, val);
|
||||
testdbGetFieldEqual("test_ai_rec.RVAL", DBF_LONG, rval);
|
||||
|
||||
// number of tests = 14
|
||||
}
|
||||
|
||||
static void test_bpt_conversion(void){
|
||||
const float typeKdegFin = 699.550510;
|
||||
const float typeKdegF = 3.427551e+02;
|
||||
const float typeKdegCin = 2902.787322;
|
||||
const float typeKdegC = 7.028131e+02;
|
||||
const float typeJdegFin = 1218.865107;
|
||||
const float typeJdegF = 3.897504e+02;
|
||||
const float typeJdegCin = 4042.988372;
|
||||
const float typeJdegC = 6.918437e+02;
|
||||
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
|
||||
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
|
||||
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
|
||||
|
||||
/* set unit conversion parameters */
|
||||
testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, 0);
|
||||
testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, 1);
|
||||
testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, 0);
|
||||
|
||||
/* Set type K deg F break point table and verify */
|
||||
testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeKdegF);
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeKdegFin);
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeKdegF);
|
||||
|
||||
/* Set type K deg C break point table and verify */
|
||||
testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeKdegC);
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeKdegCin);
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeKdegC);
|
||||
|
||||
/* Set type J deg F break point table and verify */
|
||||
testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeJdegF);
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeJdegFin);
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeJdegF);
|
||||
|
||||
/* Set type J deg C break point table and verify */
|
||||
testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeJdegC);
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeJdegCin);
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeJdegC);
|
||||
|
||||
// number of tests = 18
|
||||
}
|
||||
|
||||
static void test_smoothing_filter(void){
|
||||
const double smoo = 0.7;
|
||||
const short roff = 1;
|
||||
const short aslo = 3;
|
||||
const short aoff = -2;
|
||||
const short eslo = 2;
|
||||
const short eoff = 6;
|
||||
|
||||
const short rval1 = 7;
|
||||
const short val1 = ((((rval1 + roff) * aslo) + aoff) * eslo) + eoff;
|
||||
const short rval2 = 9;
|
||||
const short val2_pre_smoo = ((((rval2 + roff) * aslo) + aoff) * eslo) + eoff;
|
||||
const float val2 = val1 * smoo + (1 - smoo) * val2_pre_smoo;
|
||||
|
||||
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
|
||||
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
|
||||
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
|
||||
|
||||
/* set unit conversion parameters */
|
||||
testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, roff);
|
||||
testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, aslo);
|
||||
testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, aoff);
|
||||
testdbPutFieldOk("test_ai_rec.ESLO", DBF_LONG, eslo);
|
||||
testdbPutFieldOk("test_ai_rec.EOFF", DBF_LONG, eoff);
|
||||
testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertSLOPE);
|
||||
|
||||
/* set well known starting point (without smmothing) */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_SHORT, rval1);
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_SHORT, val1);
|
||||
|
||||
/* set SMOO */
|
||||
testdbPutFieldOk("test_ai_rec.SMOO", DBF_DOUBLE, smoo);
|
||||
|
||||
/* update value again and verify smoothing */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_SHORT, rval2);
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, val2);
|
||||
|
||||
/* clear SMOO */
|
||||
testdbPutFieldOk("test_ai_rec.SMOO", DBF_DOUBLE, 0.0);
|
||||
|
||||
// number of tests = 15
|
||||
}
|
||||
|
||||
static void test_udf(void){
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_ai_rec2.DTYP", DBF_STRING, "Raw Soft Channel");
|
||||
testdbPutFieldOk("test_ai_rec2.INP", DBF_STRING, "test_ai_link_rec2.VAL");
|
||||
testdbPutFieldOk("test_ai_link_rec2.FLNK", DBF_STRING, "test_ai_rec2");
|
||||
|
||||
/* verify UDF */
|
||||
testdbGetFieldEqual("test_ai_rec2.UDF", DBF_CHAR, TRUE);
|
||||
|
||||
/* set VAL and verify UDF is cleared */
|
||||
testdbPutFieldOk("test_ai_link_rec2.VAL", DBF_FLOAT, 7.0);
|
||||
testdbGetFieldEqual("test_ai_rec2.UDF", DBF_CHAR, FALSE);
|
||||
|
||||
// number of tests = 6
|
||||
}
|
||||
|
||||
static void test_alarm(void){
|
||||
const double h = 5000;
|
||||
const double hh = 7500;
|
||||
const double l = 200;
|
||||
const double ll = -20;
|
||||
const double hyst = 5;
|
||||
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Soft Channel");
|
||||
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
|
||||
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
|
||||
|
||||
/* set alarm parameters */
|
||||
testdbPutFieldOk("test_ai_rec.HIGH", DBF_DOUBLE, h);
|
||||
testdbPutFieldOk("test_ai_rec.HIHI", DBF_DOUBLE, hh);
|
||||
testdbPutFieldOk("test_ai_rec.LOW", DBF_DOUBLE, l);
|
||||
testdbPutFieldOk("test_ai_rec.LOLO", DBF_DOUBLE, ll);
|
||||
testdbPutFieldOk("test_ai_rec.HHSV", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
testdbPutFieldOk("test_ai_rec.HSV", DBF_SHORT, menuAlarmSevrMINOR);
|
||||
testdbPutFieldOk("test_ai_rec.LSV", DBF_SHORT, menuAlarmSevrMINOR);
|
||||
testdbPutFieldOk("test_ai_rec.LLSV", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
|
||||
/* set non alarm VAL and verify */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 400.0);
|
||||
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrNO_ALARM);
|
||||
|
||||
/* set LOW alarm VAL and verify */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 190.0);
|
||||
testdbGetFieldEqual("test_ai_rec.VAL", DBF_DOUBLE, 190.0);
|
||||
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMINOR);
|
||||
|
||||
/* set LOLO alarm VAL and verify */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, -9998.0);
|
||||
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
|
||||
/* set HIGH alarm VAL and verify */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 6111.0);
|
||||
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMINOR);
|
||||
|
||||
/* set HIHI alarm VAL and verify */
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 19998.0);
|
||||
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
|
||||
/* set HYST, verify and clear */
|
||||
testdbPutFieldOk("test_ai_rec.HYST", DBF_DOUBLE, hyst);
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, hh - 1.0);
|
||||
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
testdbPutFieldOk("test_ai_rec.HYST", DBF_DOUBLE, 0.0);
|
||||
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, hh - 2.0);
|
||||
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMINOR);
|
||||
|
||||
/* verify LALM */
|
||||
testdbGetFieldEqual("test_ai_rec.LALM", DBF_DOUBLE, h);
|
||||
|
||||
// number of tests = 29
|
||||
}
|
||||
|
||||
static void test_aftc(void){
|
||||
const double h = 5000;
|
||||
const double hh = 7500;
|
||||
const double l = 200;
|
||||
const double ll = -20;
|
||||
const double aftc = 3;
|
||||
testMonitor* test_mon = NULL;
|
||||
epicsTimeStamp startTime;
|
||||
epicsTimeStamp endTime;
|
||||
double diffTime = 0;
|
||||
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_ai_rec3.DTYP", DBF_STRING, "Raw Soft Channel");
|
||||
testdbPutFieldOk("test_ai_rec3.INP", DBF_STRING, "test_ai_link_rec3.VAL");
|
||||
testdbPutFieldOk("test_ai_link_rec3.FLNK", DBF_STRING, "test_ai_rec3");
|
||||
|
||||
/* set alarm parameters */
|
||||
testdbPutFieldOk("test_ai_rec3.HIGH", DBF_DOUBLE, h);
|
||||
testdbPutFieldOk("test_ai_rec3.HIHI", DBF_DOUBLE, hh);
|
||||
testdbPutFieldOk("test_ai_rec3.LOW", DBF_DOUBLE, l);
|
||||
testdbPutFieldOk("test_ai_rec3.LOLO", DBF_DOUBLE, ll);
|
||||
testdbPutFieldOk("test_ai_rec3.HHSV", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
testdbPutFieldOk("test_ai_rec3.HSV", DBF_SHORT, menuAlarmSevrMINOR);
|
||||
testdbPutFieldOk("test_ai_rec3.LSV", DBF_SHORT, menuAlarmSevrMINOR);
|
||||
testdbPutFieldOk("test_ai_rec3.LLSV", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
|
||||
/* test AFTC using a monitor and time stamps */
|
||||
testdbPutFieldOk("test_ai_rec3.AFTC", DBF_DOUBLE, aftc);
|
||||
testdbPutFieldOk("test_ai_rec3.SCAN", DBF_SHORT, menuScan_1_second);
|
||||
|
||||
/* set HIHI alarm VAL */
|
||||
testdbPutFieldOk("test_ai_link_rec3.VAL", DBF_DOUBLE, 7550.0);
|
||||
|
||||
/* Create test monitor for alarm SEVR */
|
||||
test_mon = testMonitorCreate("test_ai_rec3.VAL", DBE_ALARM, 0);
|
||||
|
||||
/* Get start time */
|
||||
epicsTimeGetCurrent(&startTime);
|
||||
|
||||
/* wait for monitor to trigger on the new alarm status*/
|
||||
testMonitorWait(test_mon);
|
||||
epicsTimeGetCurrent(&endTime);
|
||||
|
||||
/* Verify that alarm status is now MAJOR */
|
||||
testdbGetFieldEqual("test_ai_rec3.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
|
||||
/* set HI alarm VAL */
|
||||
testdbPutFieldOk("test_ai_link_rec3.VAL", DBF_DOUBLE, 5550.0);
|
||||
|
||||
/* Create test monitor for alarm SEVR */
|
||||
test_mon = testMonitorCreate("test_ai_rec3.VAL", DBE_ALARM, 0);
|
||||
|
||||
/* Get start time */
|
||||
epicsTimeGetCurrent(&startTime);
|
||||
|
||||
/* wait for monitor to trigger on the new alarm status*/
|
||||
testMonitorWait(test_mon);
|
||||
epicsTimeGetCurrent(&endTime);
|
||||
|
||||
/* Verify that alarm status is now MINOR */
|
||||
testdbGetFieldEqual("test_ai_rec3.SEVR", DBF_SHORT, menuAlarmSevrMINOR);
|
||||
|
||||
/* Verify that time is at least equal to configured aftc */
|
||||
diffTime = epicsTimeDiffInSeconds(&endTime, &startTime);
|
||||
testOk(diffTime >= aftc, "ATFC time %lf", diffTime);
|
||||
|
||||
// number of tests = 18
|
||||
}
|
||||
|
||||
MAIN(aiTest) {
|
||||
|
||||
#ifdef _WIN32
|
||||
#if (defined(_MSC_VER) && _MSC_VER < 1900) || \
|
||||
(defined(_MINGW) && defined(_TWO_DIGIT_EXPONENT))
|
||||
_set_output_format(_TWO_DIGIT_EXPONENT);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
testPlan(6+6+11+10+12+14+18+15+6+29+18);
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("aiTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
test_soft_input();
|
||||
test_raw_soft_input();
|
||||
test_operator_display();
|
||||
test_no_linr_unit_conversion();
|
||||
test_slope_linr_unit_conversion();
|
||||
test_linear_linr_unit_conversion();
|
||||
test_bpt_conversion();
|
||||
test_smoothing_filter();
|
||||
test_udf();
|
||||
test_alarm();
|
||||
test_aftc();
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
|
||||
return testDone();
|
||||
}
|
||||
20
modules/database/test/std/rec/aiTest.db
Normal file
20
modules/database/test/std/rec/aiTest.db
Normal file
@@ -0,0 +1,20 @@
|
||||
record(ai, "test_ai_rec") {
|
||||
}
|
||||
|
||||
record(ao, "test_ai_link_rec") {
|
||||
|
||||
}
|
||||
|
||||
record(ai, "test_ai_rec2") {
|
||||
}
|
||||
|
||||
record(ao, "test_ai_link_rec2") {
|
||||
|
||||
}
|
||||
|
||||
record(ai, "test_ai_rec3") {
|
||||
}
|
||||
|
||||
record(ao, "test_ai_link_rec3") {
|
||||
|
||||
}
|
||||
122
modules/database/test/std/rec/biTest.c
Normal file
122
modules/database/test/std/rec/biTest.c
Normal file
@@ -0,0 +1,122 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2023 Karl Vestin
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#include "dbUnitTest.h"
|
||||
#include "testMain.h"
|
||||
#include "errlog.h"
|
||||
#include "dbAccess.h"
|
||||
#include "menuAlarmSevr.h"
|
||||
|
||||
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
static void test_soft_input(void){
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_bi_rec.DTYP", DBF_STRING, "Soft Channel");
|
||||
testdbPutFieldOk("test_bi_rec.INP", DBF_STRING, "test_bi_link_rec.VAL");
|
||||
testdbPutFieldOk("test_bi_link_rec.FLNK", DBF_STRING, "test_bi_rec");
|
||||
|
||||
/* set VAL to on linked record */
|
||||
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE);
|
||||
|
||||
/* verify that this record VAL is updated but RVAL is not */
|
||||
testdbGetFieldEqual("test_bi_rec.VAL", DBF_SHORT, TRUE);
|
||||
testdbGetFieldEqual("test_bi_rec.RVAL", DBF_SHORT, FALSE);
|
||||
|
||||
// number of tests = 6
|
||||
}
|
||||
|
||||
static void test_raw_soft_input(void){
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_bi_rec.DTYP", DBF_STRING, "Raw Soft Channel");
|
||||
testdbPutFieldOk("test_bi_rec.INP", DBF_STRING, "test_bi_link_rec.VAL");
|
||||
testdbPutFieldOk("test_bi_link_rec.FLNK", DBF_STRING, "test_bi_rec");
|
||||
|
||||
/* set VAL to on linked record */
|
||||
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE);
|
||||
|
||||
/* verify that this record RVAL and VAL are updated */
|
||||
testdbGetFieldEqual("test_bi_rec.VAL", DBF_SHORT, TRUE);
|
||||
testdbGetFieldEqual("test_bi_rec.RVAL", DBF_SHORT, TRUE);
|
||||
|
||||
// number of tests = 6
|
||||
}
|
||||
|
||||
static void test_operator_display(void){
|
||||
/* set operator display parameters */
|
||||
testdbPutFieldOk("test_bi_rec.ZNAM", DBF_STRING, "ZNAM_TEST");
|
||||
testdbPutFieldOk("test_bi_rec.ONAM", DBF_STRING, "ONAM_TEST");
|
||||
testdbPutFieldOk("test_bi_rec.DESC", DBF_STRING, "DESC_TEST");
|
||||
|
||||
/* verify operator display parameters */
|
||||
testdbGetFieldEqual("test_bi_rec.ZNAM", DBF_STRING, "ZNAM_TEST");
|
||||
testdbGetFieldEqual("test_bi_rec.ONAM", DBF_STRING, "ONAM_TEST");
|
||||
testdbGetFieldEqual("test_bi_rec.NAME", DBF_STRING, "test_bi_rec");
|
||||
testdbGetFieldEqual("test_bi_rec.DESC", DBF_STRING, "DESC_TEST");
|
||||
|
||||
/* verify conversion */
|
||||
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE);
|
||||
testdbGetFieldEqual("test_bi_rec.VAL", DBF_STRING, "ONAM_TEST");
|
||||
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, FALSE);
|
||||
testdbGetFieldEqual("test_bi_rec.VAL", DBF_STRING, "ZNAM_TEST");
|
||||
|
||||
// number of tests = 11
|
||||
}
|
||||
|
||||
static void test_alarm(void){
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_bi_rec.DTYP", DBF_STRING, "Soft Channel");
|
||||
testdbPutFieldOk("test_bi_rec.INP", DBF_STRING, "test_bi_link_rec.VAL");
|
||||
testdbPutFieldOk("test_bi_link_rec.FLNK", DBF_STRING, "test_bi_rec");
|
||||
|
||||
/* set start VAL to FALSE*/
|
||||
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, FALSE);
|
||||
|
||||
/* set alarm parameters */
|
||||
testdbPutFieldOk("test_bi_rec.ZSV", DBF_SHORT, menuAlarmSevrNO_ALARM);
|
||||
testdbPutFieldOk("test_bi_rec.OSV", DBF_SHORT, menuAlarmSevrMINOR);
|
||||
testdbPutFieldOk("test_bi_rec.COSV", DBF_SHORT, menuAlarmSevrINVALID);
|
||||
|
||||
/* verify alarm status is NO_ALARM*/
|
||||
testdbGetFieldEqual("test_bi_rec.SEVR", DBF_SHORT, menuAlarmSevrNO_ALARM);
|
||||
|
||||
/* set ZSV to MAJOR and verify that SEVR is now MAJOR */
|
||||
testdbPutFieldOk("test_bi_rec.ZSV", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
testdbGetFieldEqual("test_bi_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
|
||||
/* set VAL to 1 on linked record and verify that COSV now sets the SEVR to INVALID */
|
||||
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE);
|
||||
testdbGetFieldEqual("test_bi_rec.SEVR", DBF_SHORT, menuAlarmSevrINVALID);
|
||||
|
||||
/* verify LAML */
|
||||
testdbGetFieldEqual("test_bi_rec.LALM", DBF_SHORT, TRUE);
|
||||
|
||||
// number of tests = 13
|
||||
}
|
||||
|
||||
MAIN(biTest) {
|
||||
|
||||
testPlan(6+6+11+13);
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("biTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
test_soft_input();
|
||||
test_raw_soft_input();
|
||||
test_operator_display();
|
||||
test_alarm();
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
|
||||
return testDone();
|
||||
}
|
||||
6
modules/database/test/std/rec/biTest.db
Normal file
6
modules/database/test/std/rec/biTest.db
Normal file
@@ -0,0 +1,6 @@
|
||||
record(bi, "test_bi_rec") {
|
||||
}
|
||||
|
||||
record(bo, "test_bi_link_rec") {
|
||||
|
||||
}
|
||||
158
modules/database/test/std/rec/boTest.c
Normal file
158
modules/database/test/std/rec/boTest.c
Normal file
@@ -0,0 +1,158 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2023 Karl Vestin
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#include "dbUnitTest.h"
|
||||
#include "testMain.h"
|
||||
#include "errlog.h"
|
||||
#include "dbAccess.h"
|
||||
#include "menuAlarmSevr.h"
|
||||
#include "menuIvoa.h"
|
||||
|
||||
#include "boRecord.h"
|
||||
|
||||
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
static void test_soft_output(void){
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_bo_rec.DTYP", DBF_STRING, "Soft Channel");
|
||||
testdbPutFieldOk("test_bo_rec.OUT", DBF_STRING, "test_bo_link_rec");
|
||||
|
||||
/* set VAL to process record */
|
||||
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
|
||||
|
||||
/* verify that OUT record is updated */
|
||||
testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, TRUE);
|
||||
|
||||
// number of tests = 4
|
||||
}
|
||||
|
||||
static void test_high(void){
|
||||
const int high_time = 2;
|
||||
testMonitor* test_mon = NULL;
|
||||
epicsTimeStamp startTime;
|
||||
epicsTimeStamp endTime;
|
||||
double diffTime = 0;
|
||||
double diffTimeTolerance = 0.1;
|
||||
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_bo_rec.DTYP", DBF_STRING, "Soft Channel");
|
||||
testdbPutFieldOk("test_bo_rec.OUT", DBF_STRING, "test_bo_link_rec");
|
||||
|
||||
/* set HIGH to 2 seconds */
|
||||
testdbPutFieldOk("test_bo_rec.HIGH", DBF_SHORT, high_time);
|
||||
|
||||
/* Create test monitor */
|
||||
test_mon = testMonitorCreate("test_bo_rec.VAL", DBR_SHORT, 0);
|
||||
|
||||
/* Get start time */
|
||||
epicsTimeGetCurrent(&startTime);
|
||||
|
||||
/* set VAL to process record */
|
||||
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
|
||||
|
||||
/* wait and verfiy time */
|
||||
testMonitorWait(test_mon);
|
||||
epicsTimeGetCurrent(&endTime);
|
||||
|
||||
/* EPICS timers have a tendency to trip slightly early, hence the test tolerance is added here to avoid false positives in testing */
|
||||
diffTime = epicsTimeDiffInSeconds(&endTime, &startTime) + diffTimeTolerance;
|
||||
testOk(diffTime >= high_time, "HIGH time %lf", diffTime);
|
||||
|
||||
/* verify that both records are set back to 0 */
|
||||
testdbGetFieldEqual("test_bo_rec.VAL", DBF_SHORT, FALSE);
|
||||
testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, FALSE);
|
||||
|
||||
testMonitorDestroy(test_mon);
|
||||
// number of tests = 7
|
||||
}
|
||||
|
||||
static void test_operator_display(void){
|
||||
/* set operator display parameters */
|
||||
testdbPutFieldOk("test_bo_rec.ZNAM", DBF_STRING, "ZNAM_TEST");
|
||||
testdbPutFieldOk("test_bo_rec.ONAM", DBF_STRING, "ONAM_TEST");
|
||||
testdbPutFieldOk("test_bo_rec.DESC", DBF_STRING, "DESC_TEST");
|
||||
|
||||
/* verify operator display parameters */
|
||||
testdbGetFieldEqual("test_bo_rec.ZNAM", DBF_STRING, "ZNAM_TEST");
|
||||
testdbGetFieldEqual("test_bo_rec.ONAM", DBF_STRING, "ONAM_TEST");
|
||||
testdbGetFieldEqual("test_bo_rec.NAME", DBF_STRING, "test_bo_rec");
|
||||
testdbGetFieldEqual("test_bo_rec.DESC", DBF_STRING, "DESC_TEST");
|
||||
|
||||
/* verify conversion */
|
||||
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
|
||||
testdbGetFieldEqual("test_bo_rec.VAL", DBF_STRING, "ONAM_TEST");
|
||||
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, FALSE);
|
||||
testdbGetFieldEqual("test_bo_rec.VAL", DBF_STRING, "ZNAM_TEST");
|
||||
|
||||
// number of tests = 11
|
||||
}
|
||||
|
||||
static void test_alarm(void){
|
||||
/* set soft channel */
|
||||
testdbPutFieldOk("test_bo_rec.DTYP", DBF_STRING, "Soft Channel");
|
||||
testdbPutFieldOk("test_bo_rec.OUT", DBF_STRING, "test_bo_link_rec");
|
||||
|
||||
/* Set start VAL to FALSE*/
|
||||
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, FALSE);
|
||||
|
||||
/* set alarm parameters */
|
||||
testdbPutFieldOk("test_bo_rec.ZSV", DBF_SHORT, menuAlarmSevrNO_ALARM);
|
||||
testdbPutFieldOk("test_bo_rec.OSV", DBF_SHORT, menuAlarmSevrMINOR);
|
||||
testdbPutFieldOk("test_bo_rec.COSV", DBF_SHORT, menuAlarmSevrINVALID);
|
||||
testdbPutFieldOk("test_bo_rec.IVOA", DBF_SHORT, menuIvoaSet_output_to_IVOV);
|
||||
testdbPutFieldOk("test_bo_rec.IVOV", DBF_SHORT, FALSE);
|
||||
|
||||
/* Verify alarm status is NO_ALARM*/
|
||||
testdbGetFieldEqual("test_bo_rec.SEVR", DBF_SHORT, menuAlarmSevrNO_ALARM);
|
||||
|
||||
/* Set ZSV to MAJOR and verify that SEVR is now MAJOR */
|
||||
testdbPutFieldOk("test_bo_rec.ZSV", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
testdbGetFieldEqual("test_bo_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
|
||||
|
||||
/* Set VAL to 1 and verify that COSV now sets the SEVR to INVALID and in turn triggers the IVOV on output */
|
||||
testdbPutFieldOk("test_bo_link_rec.VAL", DBF_SHORT, TRUE);
|
||||
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
|
||||
testdbGetFieldEqual("test_bo_rec.SEVR", DBF_SHORT, menuAlarmSevrINVALID);
|
||||
testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, FALSE);
|
||||
|
||||
/* verify IVOV continue normally action */
|
||||
testdbPutFieldOk("test_bo_rec.IVOA", DBF_SHORT, menuIvoaContinue_normally);
|
||||
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
|
||||
testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, TRUE);
|
||||
|
||||
/* verify IVOV dont drive outputs action */
|
||||
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, FALSE);
|
||||
testdbPutFieldOk("test_bo_rec.IVOA", DBF_SHORT, menuIvoaDon_t_drive_outputs);
|
||||
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
|
||||
testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, FALSE);
|
||||
|
||||
// number of tests = 22
|
||||
}
|
||||
|
||||
MAIN(boTest) {
|
||||
|
||||
testPlan(4+7+11+22);
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("boTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
test_soft_output();
|
||||
test_high();
|
||||
test_operator_display();
|
||||
test_alarm();
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
|
||||
return testDone();
|
||||
}
|
||||
6
modules/database/test/std/rec/boTest.db
Normal file
6
modules/database/test/std/rec/boTest.db
Normal file
@@ -0,0 +1,6 @@
|
||||
record(bo, "test_bo_rec") {
|
||||
}
|
||||
|
||||
record(bi, "test_bo_link_rec") {
|
||||
|
||||
}
|
||||
@@ -5,17 +5,24 @@
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cantProceed.h"
|
||||
#include "dbUnitTest.h"
|
||||
#include "testMain.h"
|
||||
#include "dbLock.h"
|
||||
#include "errlog.h"
|
||||
#include "dbAccess.h"
|
||||
#include "epicsMath.h"
|
||||
#include "menuYesNo.h"
|
||||
|
||||
#include "aiRecord.h"
|
||||
#include "waveformRecord.h"
|
||||
#include "compressRecord.h"
|
||||
|
||||
#define testDEq(A,B,D) testOk(fabs((A)-(B))<(D), #A " (%f) ~= " #B " (%f)", A, B)
|
||||
#define fetchRecordOrDie(recname, addr) if (dbNameToAddr(recname, &addr)) {testAbort("Unknown PV '%s'", recname);}
|
||||
|
||||
|
||||
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
@@ -33,8 +40,7 @@ void checkArrD(const char *pv, long elen, double a, double b, double c, double d
|
||||
expect[2] = c;
|
||||
expect[3] = d;
|
||||
|
||||
if (dbNameToAddr(pv, &addr))
|
||||
testAbort("Unknown PV '%s'", pv);
|
||||
fetchRecordOrDie(pv, addr);
|
||||
|
||||
if (dbGet(&addr, DBR_DOUBLE, buf, NULL, &nReq, NULL))
|
||||
testAbort("Failed to get '%s'", pv);
|
||||
@@ -66,8 +72,7 @@ void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32
|
||||
expect[2] = c;
|
||||
expect[3] = d;
|
||||
|
||||
if (dbNameToAddr(pv, &addr))
|
||||
testAbort("Unknown PV '%s'", pv);
|
||||
fetchRecordOrDie(pv, addr);
|
||||
|
||||
if (dbGet(&addr, DBR_LONG, buf, NULL, &nReq, NULL))
|
||||
testAbort("Failed to get '%s'", pv);
|
||||
@@ -85,6 +90,24 @@ void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
writeToWaveform(DBADDR *addr, long count, ...) {
|
||||
va_list args;
|
||||
long i;
|
||||
double *values = (double *)callocMustSucceed(count, sizeof(double), "writeToWaveform");
|
||||
|
||||
va_start(args, count);
|
||||
for (i=0; i< count; i++) {
|
||||
values[i] = va_arg(args, double);
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
dbScanLock(addr->precord);
|
||||
dbPut(addr, DBR_DOUBLE, values, count);
|
||||
dbScanUnlock(addr->precord);
|
||||
free(values);
|
||||
}
|
||||
|
||||
static
|
||||
void testFIFOCirc(void)
|
||||
{
|
||||
@@ -100,9 +123,9 @@ void testFIFOCirc(void)
|
||||
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("compressTest.db", NULL, "ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4");
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=ai,ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4");
|
||||
|
||||
vrec = (aiRecord*)testdbRecordPtr("val");
|
||||
vrec = (aiRecord*)testdbRecordPtr("ai");
|
||||
crec = (compressRecord*)testdbRecordPtr("comp");
|
||||
|
||||
eltc(0);
|
||||
@@ -230,9 +253,9 @@ void testLIFOCirc(void)
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("compressTest.db", NULL,
|
||||
"ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4");
|
||||
"INP=ai,ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4");
|
||||
|
||||
vrec = (aiRecord*)testdbRecordPtr("val");
|
||||
vrec = (aiRecord*)testdbRecordPtr("ai");
|
||||
crec = (compressRecord*)testdbRecordPtr("comp");
|
||||
|
||||
eltc(0);
|
||||
@@ -346,10 +369,278 @@ void testLIFOCirc(void)
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
void
|
||||
testArrayAverage(void) {
|
||||
DBADDR wfaddr, caddr;
|
||||
|
||||
testDiag("Test Array Average");
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=Average,BALG=FIFO Buffer,NSAM=4,N=2");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
fetchRecordOrDie("wf", wfaddr);
|
||||
fetchRecordOrDie("comp", caddr);
|
||||
|
||||
writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
|
||||
writeToWaveform(&wfaddr, 4, 2., 4., 6., 8.);
|
||||
|
||||
dbProcess(caddr.precord);
|
||||
|
||||
checkArrD("comp", 4, 1.5, 3., 4.5, 6.);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
void
|
||||
testNto1Average(void) {
|
||||
double buf = 0.0;
|
||||
long nReq = 1;
|
||||
DBADDR wfaddr, caddr;
|
||||
|
||||
testDiag("Test Average");
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
fetchRecordOrDie("wf", wfaddr);
|
||||
fetchRecordOrDie("comp", caddr);
|
||||
|
||||
testDiag("Test incomplete input data");
|
||||
|
||||
writeToWaveform(&wfaddr, 3, 1., 2., 3.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testOk1(nReq == 0);
|
||||
testDEq(buf, 0., 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testDiag("Test complete input data");
|
||||
|
||||
writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
nReq = 1;
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 2.5, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testDiag("Test single input data");
|
||||
|
||||
writeToWaveform(&wfaddr, 1, 5.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
nReq = 1;
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
// Assert that nothing has changed from before
|
||||
testDEq(buf, 2.5, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
void
|
||||
testNto1AveragePartial(void) {
|
||||
double buf = 0.0;
|
||||
long nReq = 1;
|
||||
DBADDR wfaddr, caddr;
|
||||
|
||||
testDiag("Test Average, Partial");
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4,PBUF=YES");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
testDiag("Test incomplete input data");
|
||||
|
||||
fetchRecordOrDie("wf", wfaddr);
|
||||
fetchRecordOrDie("comp", caddr);
|
||||
|
||||
writeToWaveform(&wfaddr, 3, 1., 2., 3.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 2.0, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testDiag("Test single entry from wf record");
|
||||
|
||||
writeToWaveform(&wfaddr, 1, 6.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 6.0, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
void
|
||||
testNto1LowValue(void) {
|
||||
double buf = 0.0;
|
||||
long nReq = 1;
|
||||
DBADDR wfaddr, caddr;
|
||||
|
||||
testDiag("Test 'N to 1 Low Value'");
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Low Value,BALG=FIFO Buffer,NSAM=1,N=4");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
fetchRecordOrDie("wf", wfaddr);
|
||||
fetchRecordOrDie("comp", caddr);
|
||||
|
||||
testDiag("Test full array");
|
||||
|
||||
writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 1.0, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
writeToWaveform(&wfaddr, 4, 4., 3., 2., 1.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 1.0, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testDiag("Test partial data with PBUF set to NO");
|
||||
|
||||
writeToWaveform(&wfaddr, 3, 5., 6., 7.);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
// We confirm that this hasn't changed i.e. the dbProcess above did nothing
|
||||
testDEq(buf, 1.0, 0.01);
|
||||
|
||||
testDiag("Test partial data with PBUF set to YES");
|
||||
|
||||
((compressRecord *)caddr.precord)->pbuf = menuYesNoYES;
|
||||
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
|
||||
testDEq(buf, 5.0, 0.01);
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
void
|
||||
testAIAveragePartial(void) {
|
||||
double buf = 0.;
|
||||
double data[5] = {1., 2., 3., 4., 5.};
|
||||
/*
|
||||
* Note that the fifth dbPut essentially resets the circular buffer, so the
|
||||
* average is once again the average of the _first_ entry alone.
|
||||
*/
|
||||
double expected[5] = {1., 1.5, 2., 2.5, 5.};
|
||||
long nReq = 1;
|
||||
int i;
|
||||
DBADDR aiaddr, caddr;
|
||||
|
||||
testDiag("Test 'N to 1 Average' with analog in, PBUF=YES");
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("compressTest.db", NULL, "INP=ai,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4,PBUF=YES");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
fetchRecordOrDie("ai", aiaddr);
|
||||
fetchRecordOrDie("comp", caddr);
|
||||
|
||||
for (i = 0; i < 5; i++) {
|
||||
dbScanLock(aiaddr.precord);
|
||||
dbPut(&aiaddr, DBR_DOUBLE, &data[i], 1);
|
||||
dbScanUnlock(aiaddr.precord);
|
||||
|
||||
dbScanLock(caddr.precord);
|
||||
dbProcess(caddr.precord);
|
||||
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
|
||||
testAbort("dbGet failed on compress record");
|
||||
dbScanUnlock(caddr.precord);
|
||||
|
||||
testDEq(buf, expected[i], 0.01);
|
||||
}
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
}
|
||||
|
||||
MAIN(compressTest)
|
||||
{
|
||||
testPlan(116);
|
||||
testPlan(132);
|
||||
testFIFOCirc();
|
||||
testLIFOCirc();
|
||||
testArrayAverage();
|
||||
testNto1Average();
|
||||
testNto1AveragePartial();
|
||||
testAIAveragePartial();
|
||||
testNto1LowValue();
|
||||
return testDone();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
record(ai, "val") {}
|
||||
record(ai, "ai") {}
|
||||
record(waveform, "wf") {
|
||||
field(FTVL, "DOUBLE")
|
||||
field(NELM, "4")
|
||||
}
|
||||
record(compress, "comp") {
|
||||
field(INP, "val NPP")
|
||||
field(INP, "$(INP) NPP")
|
||||
field(ALG, "$(ALG)")
|
||||
field(PBUF,"$(PBUF=NO)")
|
||||
field(BALG,"$(BALG)")
|
||||
field(NSAM,"$(NSAM)")
|
||||
field(N, "$(N=1)")
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ int asyncSoftTest(void);
|
||||
int simmTest(void);
|
||||
int mbbioDirectTest(void);
|
||||
int scanEventTest(void);
|
||||
int boTest(void);
|
||||
int biTest(void);
|
||||
int printfTest(void);
|
||||
int aiTest(void);
|
||||
|
||||
void epicsRunRecordTests(void)
|
||||
{
|
||||
@@ -51,5 +55,13 @@ void epicsRunRecordTests(void)
|
||||
|
||||
runTest(scanEventTest);
|
||||
|
||||
runTest(boTest);
|
||||
|
||||
runTest(biTest);
|
||||
|
||||
runTest(printfTest);
|
||||
|
||||
runTest(aiTest);
|
||||
|
||||
epicsExit(0); /* Trigger test harness */
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ MAIN(mbbioDirectTest)
|
||||
|
||||
testDiag("##### clear bit 31 (0x1f) #####");
|
||||
putN("do%u.B1F", N, 0);
|
||||
value &= ~(1<<31);
|
||||
value &= ~(1u<<31u);
|
||||
testN("val%d", N, value);
|
||||
testmbbioRecords(N, value);
|
||||
|
||||
|
||||
577
modules/database/test/std/rec/printfTest.c
Normal file
577
modules/database/test/std/rec/printfTest.c
Normal file
@@ -0,0 +1,577 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2023 Karl Vestin
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#include "dbUnitTest.h"
|
||||
#include "testMain.h"
|
||||
#include "errlog.h"
|
||||
#include "dbAccess.h"
|
||||
|
||||
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
static void test_double_percentile(void){
|
||||
const char format_string[] = "Format test string %%d";
|
||||
const char result_string[] = "Format test string %d";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 1);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_d_format(void){
|
||||
const char format_string[] = "Format test string %d";
|
||||
const char result_string[] = "Format test string 7";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 7);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_c_format(void){
|
||||
const char format_string[] = "Format test string %c";
|
||||
const char result_string[] = "Format test string R";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0, 82 is ASCII for R */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 82);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_i_format(void){
|
||||
const char format_string[] = "Format test string %i";
|
||||
const char result_string[] = "Format test string -27";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, -27);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_o_format(void){
|
||||
const char format_string[] = "Format test string %o";
|
||||
const char result_string[] = "Format test string 6777";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 06777);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_u_format(void){
|
||||
const char format_string[] = "Format test string %u";
|
||||
const char result_string[] = "Format test string 8009";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 8009);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_x_format(void){
|
||||
const char format_string[] = "Format test string %x";
|
||||
const char result_string[] = "Format test string fafa";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0xfafa);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_X_format(void){
|
||||
const char format_string[] = "Format test string %X";
|
||||
const char result_string[] = "Format test string BA";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 0x00ba);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_e_format(void){
|
||||
const char format_string[] = "Format test string %e";
|
||||
const char result_string[] = "Format test string -1.400000e+01";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, -14);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_E_format(void){
|
||||
const char format_string[] = "Format test string %E";
|
||||
const char result_string[] = "Format test string 1.992000E+03";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 1992);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_f_format(void){
|
||||
const char format_string[] = "Format test string %f";
|
||||
const char result_string[] = "Format test string -0.062000";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, -0.062);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_F_format(void){
|
||||
const char format_string[] = "Format test string %F";
|
||||
const char result_string[] = "Format test string 6729982.999000";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, 6729982.999);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
// visual studio less than 2015 does not support %F
|
||||
// mingw/gcc also fails, suspect this may be gcc version
|
||||
// related and checking __GNUC__ could resolve but
|
||||
// initial attempts didn't work so excluding mingw entirely for now
|
||||
#ifdef _WIN32
|
||||
#if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(_MINGW)
|
||||
testTodoBegin("Fails on windows with old visual studio versions and mingw");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
#ifdef _WIN32
|
||||
#if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(_MINGW)
|
||||
testTodoEnd();
|
||||
#endif
|
||||
#endif
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_g_format(void){
|
||||
const char format_string[] = "Format test string %g";
|
||||
const char result_string[] = "Format test string -0.093";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, -93e-3);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_G_format(void){
|
||||
const char format_string[] = "Format test string %G";
|
||||
const char result_string[] = "Format test string 7.2884E+08";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 728839938);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_s_format(void){
|
||||
|
||||
const char format_string[] = "Format test string %d %s";
|
||||
const char result_string[] = "Format test string 7 Molly III";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 7);
|
||||
|
||||
/* set value on inp1 */
|
||||
testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "Molly III");
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 4
|
||||
}
|
||||
|
||||
static void test_plus_flag(void){
|
||||
|
||||
const char format_string[] = "Format test string %+d";
|
||||
const char result_string[] = "Format test string +7";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 7);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_minus_flag(void){
|
||||
|
||||
const char format_string[] = "Format test string %-10d";
|
||||
const char result_string[] = "Format test string 18 ";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 18);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_space_flag(void){
|
||||
|
||||
const char format_string[] = "Format test string % d";
|
||||
const char result_string[] = "Format test string 12";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 12);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_hash_flag(void){
|
||||
|
||||
const char format_string[] = "Format test string %#o";
|
||||
const char result_string[] = "Format test string 014";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 014);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_min_width_flag(void){
|
||||
|
||||
const char format_string[] = "Format test string %04i";
|
||||
const char result_string[] = "Format test string 0003";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 3);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_prec_flag(void){
|
||||
|
||||
const char format_string[] = "Format test string %.4f";
|
||||
const char result_string[] = "Format test string 71.2000";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, 71.2);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_h_flag(void){
|
||||
|
||||
const char format_string[] = "Format test string %hx";
|
||||
const char result_string[] = "Format test string baba";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0xffbaba);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_hh_flag(void){
|
||||
|
||||
const char format_string[] = "Format test string %hhx";
|
||||
const char result_string[] = "Format test string c1";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0xffc0c1);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
#ifdef __rtems__
|
||||
testTodoBegin("Fails on UB-20 gcc-9 on RTEMS");
|
||||
#endif
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
#ifdef __rtems__
|
||||
testTodoEnd();
|
||||
#endif
|
||||
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_l_flag(void){
|
||||
|
||||
const char format_string[] = "Format test string %lx";
|
||||
const char result_string[] = "Format test string 70a1c0c1";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0x70a1c0c1);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
// number of tests = 3
|
||||
}
|
||||
|
||||
static void test_ll_flag(void){
|
||||
|
||||
const char format_string[] = "%d %s %llx";
|
||||
const char result_string[] = "2 Reperbahn ba0110baa0a1c0c1";
|
||||
const epicsInt64 value = 0xba0110baa0a1c0c1ull;
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 2);
|
||||
|
||||
/* set value on inp1 */
|
||||
testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "Reperbahn");
|
||||
|
||||
/* set value on inp2 */
|
||||
testdbPutFieldOk("test_printf_inp2_rec.VAL", DBR_INT64, value);
|
||||
testdbGetFieldEqual("test_printf_inp2_rec.VAL", DBR_INT64, value);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 6
|
||||
}
|
||||
|
||||
static void test_sizv(void){
|
||||
|
||||
const char format_string[] = "%d %s %llx";
|
||||
const char result_string[] = "99 123456789012345678901234567890 6640baa0a1";
|
||||
const epicsInt64 value = 0x06640baa0a1c0c1ull;
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 99);
|
||||
|
||||
/* set value on inp1 */
|
||||
testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "123456789012345678901234567890");
|
||||
|
||||
/* set value on inp2 */
|
||||
testdbPutFieldOk("test_printf_inp2_rec.VAL", DBR_INT64, value);
|
||||
testdbGetFieldEqual("test_printf_inp2_rec.VAL", DBR_INT64, value);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetArrFieldEqual("test_printf_rec.VAL$", DBF_CHAR, 45, 45, result_string);
|
||||
|
||||
// number of tests = 6
|
||||
}
|
||||
|
||||
static void test_all_inputs(void){
|
||||
|
||||
const char format_string[] = "%d %s %i %i %i %i %i %i %i %i";
|
||||
const char result_string[] = "0 One 2 3 4 5 6 7 8 9";
|
||||
|
||||
/* set format string */
|
||||
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
|
||||
|
||||
/* set value on inp0 */
|
||||
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 0);
|
||||
|
||||
/* set value on inp1 */
|
||||
testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "One");
|
||||
|
||||
/* set value on inp2 */
|
||||
testdbPutFieldOk("test_printf_inp2_rec.VAL", DBF_SHORT, 2);
|
||||
|
||||
/* set value on inp3 */
|
||||
testdbPutFieldOk("test_printf_inp3_rec.VAL", DBF_SHORT, 3);
|
||||
|
||||
/* set value on inp4 */
|
||||
testdbPutFieldOk("test_printf_inp4_rec.VAL", DBF_SHORT, 4);
|
||||
|
||||
/* set value on inp5 */
|
||||
testdbPutFieldOk("test_printf_inp5_rec.VAL", DBF_SHORT, 5);
|
||||
|
||||
/* set value on inp6 */
|
||||
testdbPutFieldOk("test_printf_inp6_rec.VAL", DBF_SHORT, 6);
|
||||
|
||||
/* set value on inp7 */
|
||||
testdbPutFieldOk("test_printf_inp7_rec.VAL", DBF_SHORT, 7);
|
||||
|
||||
/* set value on inp8 */
|
||||
testdbPutFieldOk("test_printf_inp8_rec.VAL", DBF_SHORT, 8);
|
||||
|
||||
/* set value on inp9 */
|
||||
testdbPutFieldOk("test_printf_inp9_rec.VAL", DBF_SHORT, 9);
|
||||
|
||||
/* verify that string is formatted as expected */
|
||||
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
|
||||
|
||||
// number of tests = 12
|
||||
}
|
||||
|
||||
MAIN(printfTest) {
|
||||
#ifdef _WIN32
|
||||
#if (defined(_MSC_VER) && _MSC_VER < 1900) || \
|
||||
(defined(_MINGW) && defined(_TWO_DIGIT_EXPONENT))
|
||||
_set_output_format(_TWO_DIGIT_EXPONENT);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
testPlan(3+3+3+3+3+3+3+3+4+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+6+6+12);
|
||||
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
testdbReadDatabase("printfTest.db", NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
test_double_percentile();
|
||||
test_c_format();
|
||||
test_d_format();
|
||||
test_i_format();
|
||||
test_o_format();
|
||||
test_u_format();
|
||||
test_x_format();
|
||||
test_X_format();
|
||||
test_e_format();
|
||||
test_E_format();
|
||||
test_f_format();
|
||||
test_F_format();
|
||||
test_g_format();
|
||||
test_G_format();
|
||||
test_s_format();
|
||||
test_plus_flag();
|
||||
test_minus_flag();
|
||||
test_space_flag();
|
||||
test_hash_flag();
|
||||
test_min_width_flag();
|
||||
test_prec_flag();
|
||||
test_h_flag();
|
||||
test_hh_flag();
|
||||
test_l_flag();
|
||||
test_ll_flag();
|
||||
test_sizv();
|
||||
test_all_inputs();
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
|
||||
return testDone();
|
||||
}
|
||||
53
modules/database/test/std/rec/printfTest.db
Normal file
53
modules/database/test/std/rec/printfTest.db
Normal file
@@ -0,0 +1,53 @@
|
||||
record(printf, "test_printf_rec") {
|
||||
field(SIZV, 45)
|
||||
field(INP0, "test_printf_inp0_rec")
|
||||
field(INP1, "test_printf_inp1_rec")
|
||||
field(INP2, "test_printf_inp2_rec")
|
||||
field(INP3, "test_printf_inp3_rec")
|
||||
field(INP4, "test_printf_inp4_rec")
|
||||
field(INP5, "test_printf_inp5_rec")
|
||||
field(INP6, "test_printf_inp6_rec")
|
||||
field(INP7, "test_printf_inp7_rec")
|
||||
field(INP8, "test_printf_inp8_rec")
|
||||
field(INP9, "test_printf_inp9_rec")
|
||||
}
|
||||
|
||||
record(ai, "test_printf_inp0_rec") {
|
||||
field(FLNK, "test_printf_rec")
|
||||
}
|
||||
|
||||
record(stringin, "test_printf_inp1_rec") {
|
||||
field(FLNK, "test_printf_rec")
|
||||
}
|
||||
|
||||
record(int64in, "test_printf_inp2_rec") {
|
||||
field(FLNK, "test_printf_rec")
|
||||
}
|
||||
|
||||
record(ai, "test_printf_inp3_rec") {
|
||||
field(FLNK, "test_printf_rec")
|
||||
}
|
||||
|
||||
record(ai, "test_printf_inp4_rec") {
|
||||
field(FLNK, "test_printf_rec")
|
||||
}
|
||||
|
||||
record(ai, "test_printf_inp5_rec") {
|
||||
field(FLNK, "test_printf_rec")
|
||||
}
|
||||
|
||||
record(ai, "test_printf_inp6_rec") {
|
||||
field(FLNK, "test_printf_rec")
|
||||
}
|
||||
|
||||
record(ai, "test_printf_inp7_rec") {
|
||||
field(FLNK, "test_printf_rec")
|
||||
}
|
||||
|
||||
record(ai, "test_printf_inp8_rec") {
|
||||
field(FLNK, "test_printf_rec")
|
||||
}
|
||||
|
||||
record(ai, "test_printf_inp9_rec") {
|
||||
field(FLNK, "test_printf_rec")
|
||||
}
|
||||
55
modules/database/test/std/rec/subproctest.c
Normal file
55
modules/database/test/std/rec/subproctest.c
Normal file
@@ -0,0 +1,55 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2022 UChicago Argonne LLC, as Operator of Argonne
|
||||
* National Laboratory.
|
||||
* SPDX-License-Identifier: EPICS
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
/* This test covers tests related to invoking subrecords
|
||||
*/
|
||||
|
||||
#include <testMain.h>
|
||||
#include <dbUnitTest.h>
|
||||
#include <dbAccess.h>
|
||||
#include <iocsh.h>
|
||||
#include "registryFunction.h"
|
||||
#include <subRecord.h>
|
||||
|
||||
static
|
||||
long subproc(subRecord *prec)
|
||||
{
|
||||
prec->proc = 77;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void subproctest_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
MAIN(subproctest)
|
||||
{
|
||||
testPlan(2);
|
||||
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("subproctest.dbd", NULL, NULL);
|
||||
subproctest_registerRecordDeviceDriver(pdbbase);
|
||||
registryFunctionAdd("subproc", (REGISTRYFUNCTION) subproc);
|
||||
testdbReadDatabase("subproctest.db", NULL, "TPRO=0");
|
||||
|
||||
testIocInitOk();
|
||||
testDiag("===== Test that invalid link in INPA field fails a put request ======");
|
||||
|
||||
testdbPutFieldFail(-1, "InvalidINPARec.PROC", DBF_LONG, 1);
|
||||
|
||||
/* Since the put to PROC above fails, subproc() never runs
|
||||
* and the value of PROC will not be set by subproc(). However,
|
||||
* the testdbPutField call above goes through, so we get a partial
|
||||
* result of the PROC field being left as 1. */
|
||||
testdbGetFieldEqual("InvalidINPARec.PROC", DBF_LONG, 1);
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
|
||||
return testDone();
|
||||
}
|
||||
4
modules/database/test/std/rec/subproctest.db
Normal file
4
modules/database/test/std/rec/subproctest.db
Normal file
@@ -0,0 +1,4 @@
|
||||
record(sub, "InvalidINPARec") {
|
||||
field(SNAM, "subproc")
|
||||
field(INPA, "nonexistent")
|
||||
}
|
||||
@@ -60,7 +60,19 @@ extern void *POSIX_Init(void *argument);
|
||||
#define CONFIGURE_APPLICATION_NEEDS_STUB_DRIVER
|
||||
#define CONFIGURE_APPLICATION_NEEDS_ZERO_DRIVER
|
||||
|
||||
/* Max FDs cannot exceed FD_SETSIZE from newlib (64) */
|
||||
/* Note: The select() system call can only be used with the first FD_SETSIZE
|
||||
* File Descriptors (newlib default is 64). Beginning RTEMS 5.1, FDs are
|
||||
* allocated sequentially. So changing this CONFIGURE parameter such
|
||||
* that CONFIGURE_MAXIMUM_FILE_DESCRIPTORS >= FD_SETSIZE will likely
|
||||
* cause applications making select() calls to fault at some point.
|
||||
*
|
||||
* IOC core components (libca and RSRV) do not make select() calls.
|
||||
*
|
||||
* Applications and driver code using poll() or other socket
|
||||
* multiplexers do not share this limitation.
|
||||
*
|
||||
* cf. https://github.com/epics-base/epics-base/issues/300
|
||||
*/
|
||||
#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 64
|
||||
#define CONFIGURE_IMFS_ENABLE_MKFIFO 2
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ static struct rtems_bsdnet_ifconfig loopback_config = {
|
||||
*/
|
||||
#if defined(__i386__)
|
||||
extern int
|
||||
rtems_ne2kpci_driver_attach (struct rtems_bsdnet_ifconfig *config, int attach);
|
||||
rtems_ne2kpci_driver_attach (struct rtems_bsdnet_ifconfig *config);
|
||||
static struct rtems_bsdnet_ifconfig ne2k_driver_config = {
|
||||
"ne2", /* name */
|
||||
rtems_ne2kpci_driver_attach, /* attach function */
|
||||
(void*)&rtems_ne2kpci_driver_attach, /* attach function */
|
||||
#if RTEMS_VERSION_INT<VERSION_INT(4,11,0,0)
|
||||
&loopback_config, /* link to next interface */
|
||||
#else
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "postfix.h"
|
||||
#include "asLib.h"
|
||||
|
||||
#undef ECHO /* from termios.h */
|
||||
|
||||
int asCheckClientIP;
|
||||
|
||||
static epicsMutexId asLock;
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
|
||||
class SingletonUntyped {
|
||||
public:
|
||||
#if __cplusplus>=201103L
|
||||
constexpr
|
||||
#endif
|
||||
SingletonUntyped () :_pInstance ( 0 ), _refCount ( 0 ) {}
|
||||
# if 0
|
||||
~SingletonUntyped () {
|
||||
@@ -112,6 +115,9 @@ public:
|
||||
epicsSingleton * _pSingleton;
|
||||
};
|
||||
friend class reference;
|
||||
#if __cplusplus>=201103L
|
||||
constexpr
|
||||
#endif
|
||||
epicsSingleton () {}
|
||||
// mutex lock/unlock pair overhead incurred
|
||||
// when either of these are called
|
||||
|
||||
@@ -26,6 +26,7 @@ ERR_S_FILES += $(LIBCOM)/as/asLib.h
|
||||
ERR_S_FILES += $(LIBCOM)/misc/epicsStdlib.h
|
||||
ERR_S_FILES += $(LIBCOM)/pool/epicsThreadPool.h
|
||||
ERR_S_FILES += $(LIBCOM)/error/errMdef.h
|
||||
ERR_S_FILES += $(LIBCOM)/error/errSymTbl.h
|
||||
ERR_S_FILES += $(TOP)/modules/database/src/ioc/db/dbAccessDefs.h
|
||||
ERR_S_FILES += $(TOP)/modules/database/src/ioc/dbStatic/dbStaticLib.h
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define RTN_SUCCESS(STATUS) ((STATUS)==0)
|
||||
|
||||
/* Module numbers start above 500 for compatibility with vxWorks errnoLib */
|
||||
#define MIN_MODULE_NUM 501
|
||||
|
||||
/* FIXME: M_xxx values could be declared as integer variables and set
|
||||
* at runtime from registration routines; the S_xxx definitions would
|
||||
@@ -32,6 +33,7 @@
|
||||
#define M_stdlib (504 << 16) /* EPICS Standard library */
|
||||
#define M_pool (505 << 16) /* Thread pool */
|
||||
#define M_time (506 << 16) /* epicsTime */
|
||||
#define M_err (507 << 16) /* Error */
|
||||
|
||||
/* ioc */
|
||||
#define M_dbAccess (511 << 16) /* Database Access Routines */
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#include "cantProceed.h"
|
||||
#include "epicsAssert.h"
|
||||
#include "epicsStdio.h"
|
||||
#include "epicsTypes.h"
|
||||
#include "epicsMutex.h"
|
||||
#include "epicsThread.h"
|
||||
#include "errMdef.h"
|
||||
#include "errSymTbl.h"
|
||||
#include "ellLib.h"
|
||||
@@ -33,69 +36,49 @@
|
||||
static epicsUInt16 errhash(long errNum);
|
||||
|
||||
typedef struct errnumnode {
|
||||
ELLNODE node;
|
||||
long errNum;
|
||||
struct errnumnode *hashnode;
|
||||
const char *message;
|
||||
long pad;
|
||||
} ERRNUMNODE;
|
||||
|
||||
static ELLLIST errnumlist = ELLLIST_INIT;
|
||||
static ERRNUMNODE **hashtable;
|
||||
static int initialized = 0;
|
||||
typedef struct {
|
||||
ERRNUMNODE *table[NHASH * sizeof(ERRNUMNODE *)];
|
||||
epicsMutexId tableMutexId;
|
||||
} errHashTable_t;
|
||||
|
||||
static errHashTable_t errHashTable;
|
||||
|
||||
extern ERRSYMTAB_ID errSymTbl;
|
||||
|
||||
/****************************************************************
|
||||
* ERRSYMBLD
|
||||
*
|
||||
* Create the normal ell LIST of sorted error messages nodes
|
||||
* Followed by linked hash lists - that link together those
|
||||
* ell nodes that have a common hash number.
|
||||
* Populate EPICS error symbols
|
||||
*
|
||||
***************************************************************/
|
||||
int errSymBld(void)
|
||||
{
|
||||
ERRSYMBOL *errArray = errSymTbl->symbols;
|
||||
ERRNUMNODE *perrNumNode = NULL;
|
||||
ERRNUMNODE *pNextNode = NULL;
|
||||
ERRNUMNODE **phashnode = NULL;
|
||||
int i;
|
||||
int modnum;
|
||||
|
||||
if (initialized)
|
||||
return(0);
|
||||
|
||||
hashtable = (ERRNUMNODE**)callocMustSucceed
|
||||
(NHASH, sizeof(ERRNUMNODE*),"errSymBld");
|
||||
ERRSYMBOL *errArray = errSymTbl->symbols;
|
||||
int i;
|
||||
for (i = 0; i < errSymTbl->nsymbols; i++, errArray++) {
|
||||
modnum = errArray->errNum >> 16;
|
||||
if (modnum < 501) {
|
||||
fprintf(stderr, "errSymBld: ERROR - Module number in errSymTbl < 501 was Module=%lx Name=%s\n",
|
||||
errArray->errNum, errArray->name);
|
||||
continue;
|
||||
}
|
||||
if ((errSymbolAdd(errArray->errNum, errArray->name)) < 0) {
|
||||
if (errSymbolAdd(errArray->errNum, errArray->name)) {
|
||||
fprintf(stderr, "errSymBld: ERROR - errSymbolAdd() failed \n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
perrNumNode = (ERRNUMNODE *) ellFirst(&errnumlist);
|
||||
while (perrNumNode) {
|
||||
/* hash each perrNumNode->errNum */
|
||||
epicsUInt16 hashInd = errhash(perrNumNode->errNum);
|
||||
|
||||
phashnode = (ERRNUMNODE**)&hashtable[hashInd];
|
||||
pNextNode = (ERRNUMNODE*) *phashnode;
|
||||
/* search for last node (NULL) of hashnode linked list */
|
||||
while (pNextNode) {
|
||||
phashnode = &pNextNode->hashnode;
|
||||
pNextNode = *phashnode;
|
||||
}
|
||||
*phashnode = perrNumNode;
|
||||
perrNumNode = (ERRNUMNODE *) ellNext((ELLNODE *) perrNumNode);
|
||||
}
|
||||
initialized = 1;
|
||||
return(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _initErrorHashTable(void *_unused)
|
||||
{
|
||||
errHashTable.tableMutexId = epicsMutexMustCreate();
|
||||
}
|
||||
|
||||
static void initErrorHashTable()
|
||||
{
|
||||
static epicsThreadOnceId initErrSymOnceFlag = EPICS_THREAD_ONCE_INIT;
|
||||
epicsThreadOnce(&initErrSymOnceFlag, _initErrorHashTable, NULL);
|
||||
}
|
||||
|
||||
/****************************************************************
|
||||
@@ -109,21 +92,48 @@ static epicsUInt16 errhash(long errNum)
|
||||
|
||||
modnum = (unsigned short) (errNum >> 16);
|
||||
errnum = (unsigned short) (errNum & 0xffff);
|
||||
return (((modnum - 500) * 20) + errnum) % NHASH;
|
||||
return ((modnum - (MIN_MODULE_NUM - 1)) * 20 + errnum) % NHASH;
|
||||
}
|
||||
|
||||
/****************************************************************
|
||||
* ERRSYMBOLADD
|
||||
* adds symbols to the master errnumlist as compiled from errSymTbl.c
|
||||
* adds symbols to the global error symbol hash table
|
||||
***************************************************************/
|
||||
int errSymbolAdd(long errNum, const char *name)
|
||||
{
|
||||
ERRNUMNODE *pNew = (ERRNUMNODE*) callocMustSucceed(1,
|
||||
sizeof(ERRNUMNODE), "errSymbolAdd");
|
||||
ERRNUMNODE *pNextNode = NULL;
|
||||
ERRNUMNODE **phashnode = NULL;
|
||||
ERRNUMNODE *pNew = NULL;
|
||||
int modnum = (epicsUInt16) (errNum >> 16);
|
||||
|
||||
if (modnum < MIN_MODULE_NUM)
|
||||
return S_err_invCode;
|
||||
|
||||
initErrorHashTable();
|
||||
|
||||
epicsUInt16 hashInd = errhash(errNum);
|
||||
|
||||
epicsMutexLock(errHashTable.tableMutexId);
|
||||
phashnode = (ERRNUMNODE**)&errHashTable.table[hashInd];
|
||||
pNextNode = (ERRNUMNODE*) *phashnode;
|
||||
|
||||
/* search for last node (NULL) of hashnode linked list */
|
||||
while (pNextNode) {
|
||||
if (pNextNode->errNum == errNum) {
|
||||
int notIdentical = strcmp(name, pNextNode->message);
|
||||
epicsMutexUnlock(errHashTable.tableMutexId);
|
||||
return notIdentical ? S_err_codeExists : 0;
|
||||
}
|
||||
phashnode = &pNextNode->hashnode;
|
||||
pNextNode = *phashnode;
|
||||
}
|
||||
|
||||
pNew = (ERRNUMNODE*) callocMustSucceed(
|
||||
1, sizeof(ERRNUMNODE), "errSymbolAdd");
|
||||
pNew->errNum = errNum;
|
||||
pNew->message = name;
|
||||
ellAdd(&errnumlist, (ELLNODE*)pNew);
|
||||
*phashnode = pNew;
|
||||
epicsMutexUnlock(errHashTable.tableMutexId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -155,31 +165,31 @@ const char* errSymLookupInternal(long status)
|
||||
if (!status)
|
||||
return "Ok";
|
||||
|
||||
if (!initialized)
|
||||
errSymBld();
|
||||
initErrorHashTable();
|
||||
|
||||
modNum = (unsigned) status;
|
||||
modNum >>= 16;
|
||||
modNum &= 0xffff;
|
||||
if (modNum <= 500) {
|
||||
const char * pStr = strerror ((int) status);
|
||||
if (pStr) {
|
||||
return pStr;
|
||||
}
|
||||
if (modNum < MIN_MODULE_NUM) {
|
||||
return strerror ((int) status);
|
||||
}
|
||||
else {
|
||||
unsigned hashInd = errhash(status);
|
||||
phashnode = (ERRNUMNODE**)&hashtable[hashInd];
|
||||
const char *result = NULL;
|
||||
epicsMutexLock(errHashTable.tableMutexId);
|
||||
phashnode = (ERRNUMNODE**)&errHashTable.table[hashInd];
|
||||
pNextNode = *phashnode;
|
||||
while (pNextNode) {
|
||||
if (pNextNode->errNum==status){
|
||||
return pNextNode->message;
|
||||
result = pNextNode->message;
|
||||
break;
|
||||
}
|
||||
phashnode = &pNextNode->hashnode;
|
||||
pNextNode = *phashnode;
|
||||
}
|
||||
epicsMutexUnlock(errHashTable.tableMutexId);
|
||||
return result;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* errSymMsg(long status)
|
||||
@@ -210,12 +220,12 @@ void errSymDump(void)
|
||||
int i;
|
||||
int msgcount = 0;
|
||||
|
||||
if (!initialized) errSymBld();
|
||||
initErrorHashTable();
|
||||
|
||||
msgcount = 0;
|
||||
printf("errSymDump: number of hash slots = %d\n", NHASH);
|
||||
for (i = 0; i < NHASH; i++) {
|
||||
ERRNUMNODE **phashnode = &hashtable[i];
|
||||
ERRNUMNODE **phashnode = &errHashTable.table[i];
|
||||
ERRNUMNODE *pNextNode = *phashnode;
|
||||
int count = 0;
|
||||
|
||||
@@ -246,14 +256,15 @@ void errSymTestPrint(long errNum)
|
||||
epicsUInt16 modnum;
|
||||
epicsUInt16 errnum;
|
||||
|
||||
if (!initialized) errSymBld();
|
||||
initErrorHashTable();
|
||||
|
||||
message[0] = '\0';
|
||||
modnum = (epicsUInt16) (errNum >> 16);
|
||||
errnum = (epicsUInt16) (errNum & 0xffff);
|
||||
if (modnum < 501) {
|
||||
if (modnum < MIN_MODULE_NUM) {
|
||||
fprintf(stderr, "Usage: errSymTestPrint(long errNum) \n");
|
||||
fprintf(stderr, "errSymTestPrint: module number < 501 \n");
|
||||
fprintf(stderr,
|
||||
"errSymTestPrint: module number < %d\n", MIN_MODULE_NUM);
|
||||
return;
|
||||
}
|
||||
errSymLookup(errNum, message, sizeof(message));
|
||||
@@ -272,10 +283,11 @@ void errSymTest(epicsUInt16 modnum, epicsUInt16 begErrNum,
|
||||
long errNum;
|
||||
epicsUInt16 errnum;
|
||||
|
||||
if (!initialized) errSymBld();
|
||||
if (modnum < 501)
|
||||
if (modnum < MIN_MODULE_NUM)
|
||||
return;
|
||||
|
||||
initErrorHashTable();
|
||||
|
||||
/* print range of error messages */
|
||||
for (errnum = begErrNum; errnum <= endErrNum; errnum++) {
|
||||
errNum = modnum << 16;
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
#include "libComAPI.h"
|
||||
#include "epicsTypes.h"
|
||||
|
||||
#define S_err_invCode (M_err | 1) /* Invalid error symbol code */
|
||||
#define S_err_codeExists (M_err | 2) /* Error code already exists */
|
||||
|
||||
/* ERRSYMBOL - entry in symbol table */
|
||||
typedef struct {
|
||||
long errNum; /* errMessage symbol number */
|
||||
|
||||
@@ -92,10 +92,11 @@ static struct {
|
||||
epicsMutexId msgQueueLock;
|
||||
|
||||
/* guarded by msgQueueLock */
|
||||
int atExit;
|
||||
int sevToLog;
|
||||
int toConsole;
|
||||
int ttyConsole;
|
||||
unsigned int atExit:1;
|
||||
unsigned int toConsole:1;
|
||||
unsigned int ttyConsole:1;
|
||||
unsigned int closeConsole:1;
|
||||
FILE *console;
|
||||
|
||||
/* A loop counter maintained by errlogThread. */
|
||||
@@ -466,7 +467,7 @@ int eltc(int yesno)
|
||||
{
|
||||
errlogInit(0);
|
||||
epicsMutexMustLock(pvt.msgQueueLock);
|
||||
pvt.toConsole = yesno;
|
||||
pvt.toConsole = yesno ? 1 : 0;
|
||||
epicsMutexUnlock(pvt.msgQueueLock);
|
||||
errlogFlush();
|
||||
return 0;
|
||||
@@ -474,9 +475,30 @@ int eltc(int yesno)
|
||||
|
||||
int errlogSetConsole(FILE *stream)
|
||||
{
|
||||
int closeConsole = 0;
|
||||
errlogInit(0);
|
||||
if (!stream) {
|
||||
stream = stderr;
|
||||
}
|
||||
#ifdef vxWorks
|
||||
/* The stderr/stdout we get may become invalid as they are thread specific.
|
||||
* Also, the FDs are actually maps to some "real" FDs.
|
||||
* Thus, we clone the stream and link it with a dup of the "real" FD.
|
||||
* The dup'ed FD will be closed when the FILE handle is fclosed. */
|
||||
if (fileno(stream) <= 2) {
|
||||
closeConsole = 1;
|
||||
stream = fdopen(dup(ioTaskStdGet(0, fileno(stream))),"w");
|
||||
if (!stream) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
epicsMutexMustLock(pvt.msgQueueLock);
|
||||
pvt.console = stream ? stream : stderr;
|
||||
if (pvt.closeConsole) {
|
||||
fclose(pvt.console);
|
||||
}
|
||||
pvt.closeConsole = closeConsole;
|
||||
pvt.console = stream;
|
||||
pvt.ttyConsole = isATTY(pvt.console);
|
||||
epicsMutexUnlock(pvt.msgQueueLock);
|
||||
/* make sure worker has stopped writing to the previous stream */
|
||||
@@ -551,7 +573,13 @@ static void errlogInitPvt(void *arg)
|
||||
pvt.maxMsgSize = pconfig->maxMsgSize;
|
||||
ellInit(&pvt.listenerList);
|
||||
pvt.toConsole = TRUE;
|
||||
#ifdef vxWorks
|
||||
pvt.console = fdopen(dup(ioTaskStdGet(0, 2)),"w");
|
||||
pvt.closeConsole = 1;
|
||||
#else
|
||||
pvt.console = stderr;
|
||||
pvt.closeConsole = 0;
|
||||
#endif
|
||||
pvt.ttyConsole = isATTY(stderr);
|
||||
pvt.waitForWork = epicsEventCreate(epicsEventEmpty);
|
||||
pvt.listenerLock = epicsMutexCreate();
|
||||
|
||||
@@ -97,7 +97,7 @@ LIBCOM_API extern int errVerbose;
|
||||
* that the output is sent to the errlog task. Unless configured not to, the output
|
||||
* will appear on the console as well.
|
||||
*/
|
||||
LIBCOM_API int errlogPrintf(const char *pformat, ...)
|
||||
LIBCOM_API int errlogPrintf(EPICS_PRINTF_FMT(const char *pformat), ...)
|
||||
EPICS_PRINTF_STYLE(1,2);
|
||||
|
||||
/**
|
||||
@@ -118,7 +118,8 @@ LIBCOM_API int errlogVprintf(const char *pformat, va_list pvar);
|
||||
* \return int Consult printf documentation in C standard library
|
||||
*/
|
||||
LIBCOM_API int errlogSevPrintf(const errlogSevEnum severity,
|
||||
const char *pformat, ...) EPICS_PRINTF_STYLE(2,3);
|
||||
EPICS_PRINTF_FMT(const char *pformat), ...
|
||||
) EPICS_PRINTF_STYLE(2,3);
|
||||
|
||||
/**
|
||||
* This function is like ::errlogVprintf except that it adds the severity to the beginning
|
||||
@@ -239,11 +240,14 @@ LIBCOM_API void errlogFlush(void);
|
||||
* The remaining arguments are just like the arguments to the C printf routine.
|
||||
* ::errVerbose determines if the filename and line number are shown.
|
||||
*/
|
||||
LIBCOM_API void errPrintf(long status, const char *pFileName, int lineno,
|
||||
const char *pformat, ...) EPICS_PRINTF_STYLE(4,5);
|
||||
LIBCOM_API void errPrintf(
|
||||
long status, const char *pFileName, int lineno,
|
||||
EPICS_PRINTF_FMT(const char *pformat), ...
|
||||
) EPICS_PRINTF_STYLE(4,5);
|
||||
|
||||
LIBCOM_API int errlogPrintfNoConsole(const char *pformat, ...)
|
||||
EPICS_PRINTF_STYLE(1,2);
|
||||
LIBCOM_API int errlogPrintfNoConsole(
|
||||
EPICS_PRINTF_FMT(const char *pformat), ...
|
||||
) EPICS_PRINTF_STYLE(1,2);
|
||||
LIBCOM_API int errlogVprintfNoConsole(const char *pformat,va_list pvar);
|
||||
|
||||
/**
|
||||
|
||||
@@ -663,7 +663,7 @@ int otoi(Char *str)
|
||||
|
||||
char *readable_form(int c)
|
||||
{
|
||||
static char rform[10];
|
||||
static char rform[16];
|
||||
|
||||
if ( (c >= 0 && c < 32) || c >= 127 )
|
||||
{
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
LIBCOM_API extern int freeListBypass;
|
||||
|
||||
LIBCOM_API void epicsStdCall freeListInitPvt(void **ppvt, int size, int malloc);
|
||||
LIBCOM_API void * epicsStdCall freeListCalloc(void *pvt);
|
||||
LIBCOM_API void * epicsStdCall freeListMalloc(void *pvt);
|
||||
|
||||
@@ -26,6 +26,20 @@
|
||||
#include "epicsMutex.h"
|
||||
#include "freeList.h"
|
||||
#include "adjustment.h"
|
||||
#include "errlog.h"
|
||||
#include "epicsString.h"
|
||||
#include "epicsAtomic.h"
|
||||
#include "epicsExport.h"
|
||||
|
||||
/* Bypass free list and directly call malloc() every time? */
|
||||
int freeListBypass
|
||||
#ifdef EPICS_FREELIST_DEBUG
|
||||
= 1;
|
||||
#else
|
||||
= 2; /* checks environment $EPICS_FREELIST_BYPASS */
|
||||
#endif
|
||||
|
||||
epicsExportAddress(int, freeListBypass);
|
||||
|
||||
typedef struct allocMem {
|
||||
struct allocMem *next;
|
||||
@@ -44,10 +58,25 @@ LIBCOM_API void epicsStdCall
|
||||
freeListInitPvt(void **ppvt,int size,int nmalloc)
|
||||
{
|
||||
FREELISTPVT *pfl;
|
||||
int bypass = epicsAtomicGetIntT(&freeListBypass);
|
||||
|
||||
if(bypass==2) {
|
||||
const char *str = getenv("EPICS_FREELIST_BYPASS");
|
||||
|
||||
if(str && epicsStrCaseCmp(str, "YES")==0) {
|
||||
bypass = 1;
|
||||
} else if(!str || str[0]=='\0' || epicsStrCaseCmp(str, "NO")==0) {
|
||||
bypass = 0;
|
||||
} else {
|
||||
errlogPrintf(ERL_WARNING " EPICS_FREELIST_BYPASS expected to be YES, NO, or empty. Not \"%s\"\n", str);
|
||||
}
|
||||
epicsAtomicSetIntT(&freeListBypass, bypass);
|
||||
}
|
||||
|
||||
pfl = callocMustSucceed(1,sizeof(FREELISTPVT), "freeListInitPvt");
|
||||
pfl->size = adjustToWorstCaseAlignment(size);
|
||||
pfl->nmalloc = nmalloc;
|
||||
if(!bypass)
|
||||
pfl->nmalloc = nmalloc; /* nmalloc==0 to bypass */
|
||||
pfl->head = NULL;
|
||||
pfl->mallochead = NULL;
|
||||
pfl->nBlocksAvailable = 0u;
|
||||
@@ -60,28 +89,26 @@ LIBCOM_API void epicsStdCall
|
||||
LIBCOM_API void * epicsStdCall freeListCalloc(void *pvt)
|
||||
{
|
||||
FREELISTPVT *pfl = pvt;
|
||||
# ifdef EPICS_FREELIST_DEBUG
|
||||
return callocMustSucceed(1,pfl->size,"freeList Debug Calloc");
|
||||
# else
|
||||
void *ptemp;
|
||||
|
||||
ptemp = freeListMalloc(pvt);
|
||||
if(ptemp) memset((char *)ptemp,0,pfl->size);
|
||||
if(!pfl->nmalloc)
|
||||
ptemp = calloc(1u, pfl->size);
|
||||
else if(!!(ptemp = freeListMalloc(pvt)))
|
||||
memset((char *)ptemp,0,pfl->size);
|
||||
return(ptemp);
|
||||
# endif
|
||||
}
|
||||
|
||||
LIBCOM_API void * epicsStdCall freeListMalloc(void *pvt)
|
||||
{
|
||||
FREELISTPVT *pfl = pvt;
|
||||
# ifdef EPICS_FREELIST_DEBUG
|
||||
return callocMustSucceed(1,pfl->size,"freeList Debug Malloc");
|
||||
# else
|
||||
void *ptemp;
|
||||
void **ppnext;
|
||||
allocMem *pallocmem;
|
||||
int i;
|
||||
|
||||
if(!pfl->nmalloc)
|
||||
return malloc(pfl->size);
|
||||
|
||||
epicsMutexMustLock(pfl->lock);
|
||||
ptemp = pfl->head;
|
||||
if(ptemp==0) {
|
||||
@@ -125,18 +152,18 @@ LIBCOM_API void * epicsStdCall freeListMalloc(void *pvt)
|
||||
VALGRIND_MEMPOOL_FREE(pfl, ptemp);
|
||||
VALGRIND_MEMPOOL_ALLOC(pfl, ptemp, pfl->size);
|
||||
return(ptemp);
|
||||
# endif
|
||||
}
|
||||
|
||||
LIBCOM_API void epicsStdCall freeListFree(void *pvt,void*pmem)
|
||||
{
|
||||
FREELISTPVT *pfl = pvt;
|
||||
# ifdef EPICS_FREELIST_DEBUG
|
||||
memset ( pmem, 0xdd, pfl->size );
|
||||
free(pmem);
|
||||
# else
|
||||
void **ppnext;
|
||||
|
||||
if(!pfl->nmalloc) {
|
||||
free(pmem);
|
||||
return;
|
||||
}
|
||||
|
||||
VALGRIND_MEMPOOL_FREE(pvt, pmem);
|
||||
VALGRIND_MEMPOOL_ALLOC(pvt, pmem, sizeof(void*));
|
||||
|
||||
@@ -146,7 +173,6 @@ LIBCOM_API void epicsStdCall freeListFree(void *pvt,void*pmem)
|
||||
pfl->head = pmem;
|
||||
pfl->nBlocksAvailable++;
|
||||
epicsMutexUnlock(pfl->lock);
|
||||
# endif
|
||||
}
|
||||
|
||||
LIBCOM_API void epicsStdCall freeListCleanup(void *pvt)
|
||||
|
||||
@@ -95,9 +95,9 @@ typedef enum {
|
||||
|
||||
initHookAtShutdown, /**< Start of iocShutdown() (unit tests only) */
|
||||
initHookAfterCloseLinks, /**< Links disabled/deleted */
|
||||
initHookAfterStopScan, /**< Scan tasks stopped */
|
||||
initHookAfterStopScan, /**< Scan tasks stopped. Prior to UNRELEASED, triggered only by unittest code. */
|
||||
initHookAfterStopCallback, /**< Callback tasks stopped */
|
||||
initHookAfterStopLinks, /**< CA links stopped */
|
||||
initHookAfterStopLinks, /**< CA links stopped. Prior to UNRELEASED, triggered only by unittest code. */
|
||||
initHookBeforeFree, /**< Resource cleanup about to happen */
|
||||
initHookAfterShutdown, /**< End of iocShutdown() */
|
||||
|
||||
|
||||
@@ -309,7 +309,7 @@ struct Tokenize {
|
||||
}
|
||||
}
|
||||
if (inword) {
|
||||
if (c == quote) {
|
||||
if (c == quote && !backslash) {
|
||||
quote = 0;
|
||||
}
|
||||
else {
|
||||
@@ -329,6 +329,10 @@ struct Tokenize {
|
||||
}
|
||||
else {
|
||||
line[icout++] = c;
|
||||
if (c == '\\' && !backslash) {
|
||||
backslash = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -559,7 +563,17 @@ char** iocsh_attempt_completion(const char* word, int start, int end)
|
||||
break;
|
||||
}
|
||||
}
|
||||
err = arg-1u >= size_t(def->pFuncDef->nargs);
|
||||
if(arg-1u >= size_t(def->pFuncDef->nargs)) {
|
||||
if(def->pFuncDef->arg
|
||||
&& def->pFuncDef->nargs
|
||||
&& def->pFuncDef->arg[def->pFuncDef->nargs-1u]->type == iocshArgArgv)
|
||||
{
|
||||
// last argument is variable length
|
||||
arg = def->pFuncDef->nargs;
|
||||
} else {
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!err) {
|
||||
|
||||
@@ -287,6 +287,7 @@ LIBCOM_API int epicsStdCall iocshRun(const char *cmd, const char* macros);
|
||||
*
|
||||
* @param err 0 - success (no op), !=0 - error
|
||||
* @return The err argument value.
|
||||
* @since 7.0.3.1
|
||||
*/
|
||||
LIBCOM_API int iocshSetError(int err);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "taskwd.h"
|
||||
#include "registry.h"
|
||||
#include "epicsGeneralTime.h"
|
||||
#include "freeList.h"
|
||||
#include "libComRegister.h"
|
||||
|
||||
/* Register the PWD environment variable when the cd IOC shell function is
|
||||
@@ -447,7 +448,7 @@ static void epicsThreadResumeCallFunc(const iocshArgBuf *args)
|
||||
}
|
||||
|
||||
/* generalTimeReport */
|
||||
static const iocshArg generalTimeReportArg0 = { "interest_level", iocshArgArgv};
|
||||
static const iocshArg generalTimeReportArg0 = { "interest_level", iocshArgInt};
|
||||
static const iocshArg * const generalTimeReportArgs[1] = { &generalTimeReportArg0 };
|
||||
static const iocshFuncDef generalTimeReportFuncDef = {"generalTimeReport",1,generalTimeReportArgs,
|
||||
"Display time providers and their priority levels"
|
||||
@@ -467,7 +468,11 @@ static void installLastResortEventProviderCallFunc(const iocshArgBuf *args)
|
||||
installLastResortEventProvider();
|
||||
}
|
||||
|
||||
static iocshVarDef asCheckClientIPDef[] = { { "asCheckClientIP", iocshArgInt, 0 }, { NULL, iocshArgInt, NULL } };
|
||||
static iocshVarDef comDefs[] = {
|
||||
{ "asCheckClientIP", iocshArgInt, 0 },
|
||||
{ "freeListBypass", iocshArgInt, 0 },
|
||||
{ NULL, iocshArgInt, NULL }
|
||||
};
|
||||
|
||||
void epicsStdCall libComRegister(void)
|
||||
{
|
||||
@@ -504,6 +509,7 @@ void epicsStdCall libComRegister(void)
|
||||
iocshRegister(&generalTimeReportFuncDef,generalTimeReportCallFunc);
|
||||
iocshRegister(&installLastResortEventProviderFuncDef, installLastResortEventProviderCallFunc);
|
||||
|
||||
asCheckClientIPDef[0].pval = &asCheckClientIP;
|
||||
iocshRegisterVariable(asCheckClientIPDef);
|
||||
comDefs[0].pval = &asCheckClientIP;
|
||||
comDefs[1].pval = &freeListBypass;
|
||||
iocshRegisterVariable(comDefs);
|
||||
}
|
||||
|
||||
@@ -113,8 +113,8 @@ int main(void)
|
||||
|
||||
pserver->pfdctx = (void *) fdmgr_init();
|
||||
if (!pserver->pfdctx) {
|
||||
free(pserver);
|
||||
fprintf(stderr, "iocLogServer: %s\n", strerror(errno));
|
||||
free(pserver);
|
||||
return IOCLS_ERROR;
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ int main(void)
|
||||
fprintf (stderr,
|
||||
"iocLogServer: a server is already installed on port %u?\n",
|
||||
(unsigned)ioc_log_port);
|
||||
free(pserver);
|
||||
return IOCLS_ERROR;
|
||||
}
|
||||
|
||||
@@ -158,6 +159,7 @@ int main(void)
|
||||
char sockErrBuf[64];
|
||||
epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) );
|
||||
fprintf(stderr, "iocLogServer: listen err %s\n", sockErrBuf);
|
||||
free(pserver);
|
||||
return IOCLS_ERROR;
|
||||
}
|
||||
|
||||
@@ -174,6 +176,7 @@ int main(void)
|
||||
char sockErrBuf[64];
|
||||
epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) );
|
||||
fprintf(stderr, "iocLogServer: ioctl FIONBIO err %s\n", sockErrBuf);
|
||||
free(pserver);
|
||||
return IOCLS_ERROR;
|
||||
}
|
||||
|
||||
@@ -190,6 +193,7 @@ int main(void)
|
||||
"File access problems to `%s' because `%s'\n",
|
||||
ioc_log_file_name,
|
||||
strerror(errno));
|
||||
free(pserver);
|
||||
return IOCLS_ERROR;
|
||||
}
|
||||
|
||||
@@ -202,6 +206,7 @@ int main(void)
|
||||
if (status < 0) {
|
||||
fprintf(stderr,
|
||||
"iocLogServer: failed to add read callback\n");
|
||||
free(pserver);
|
||||
return IOCLS_ERROR;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user