Fix watch transport
- SHA
cc129606e6155a6ac4b141d0eb5fa5f6d5872028- Parents
-
e942851 - Tree
9570725
cc12960
cc129606e6155a6ac4b141d0eb5fa5f6d5872028e942851
9570725| Status | File | + | - |
|---|---|---|---|
| M |
src/fgof_watch.f90
|
116 | 87 |
| M |
src/fgof_watch_poll.c
|
50 | 23 |
| M |
src/fgof_watch_types.f90
|
1 | 0 |
| M |
test/test_watch_filters.f90
|
29 | 1 |
| A |
test/test_watch_literal_paths.f90
|
47 | 0 |
| M |
test/watch_test_support.f90
|
23 | 0 |
src/fgof_watch.f90modified@@ -160,8 +160,10 @@ contains | ||
| 160 | 160 | |
| 161 | 161 | width = max(1, max_string_length(prefixes)) |
| 162 | 162 | allocate(character(len=width) :: options%ignore_prefixes(size(prefixes))) |
| 163 | + allocate(options%ignore_prefix_lengths(size(prefixes))) | |
| 163 | 164 | do i = 1, size(prefixes) |
| 164 | 165 | options%ignore_prefixes(i) = prefixes(i) |
| 166 | + options%ignore_prefix_lengths(i) = len(prefixes(i)) | |
| 165 | 167 | end do |
| 166 | 168 | end subroutine set_ignore_prefixes |
| 167 | 169 | |
@@ -171,6 +173,9 @@ contains | ||
| 171 | 173 | if (allocated(options%ignore_prefixes)) then |
| 172 | 174 | deallocate(options%ignore_prefixes) |
| 173 | 175 | end if |
| 176 | + if (allocated(options%ignore_prefix_lengths)) then | |
| 177 | + deallocate(options%ignore_prefix_lengths) | |
| 178 | + end if | |
| 174 | 179 | end subroutine clear_ignore_prefixes |
| 175 | 180 | |
| 176 | 181 | subroutine collect_snapshot(root, options, entries, status_code, status_message) |
@@ -187,7 +192,6 @@ contains | ||
| 187 | 192 | integer(c_int) :: prefix_count |
| 188 | 193 | integer(c_int) :: prefix_stride |
| 189 | 194 | character(kind=c_char), pointer :: raw_chars(:) |
| 190 | - character(len=:), allocatable :: text | |
| 191 | 195 | |
| 192 | 196 | c_root = to_c_string(root) |
| 193 | 197 | call pack_ignore_prefixes(options, prefix_count, prefix_stride, c_prefixes) |
@@ -221,10 +225,9 @@ contains | ||
| 221 | 225 | end if |
| 222 | 226 | |
| 223 | 227 | call c_f_pointer(raw_ptr, raw_chars, [int(raw_len)]) |
| 224 | - text = buffer_to_text(raw_chars, int(raw_len)) | |
| 228 | + call parse_snapshot_buffer(raw_chars, int(raw_len), entries) | |
| 225 | 229 | call fgof_watch_free_buffer_c(raw_ptr) |
| 226 | 230 | |
| 227 | - call parse_snapshot_text(text, entries) | |
| 228 | 231 | call filter_entries(root, options, entries) |
| 229 | 232 | call sort_entries(entries) |
| 230 | 233 | end subroutine collect_snapshot |
@@ -261,10 +264,10 @@ contains | ||
| 261 | 264 | |
| 262 | 265 | do i = 1, size(options%ignore_prefixes) |
| 263 | 266 | offset = (i - 1) * width |
| 264 | - do j = 1, len_trim(options%ignore_prefixes(i)) | |
| 267 | + do j = 1, ignore_prefix_length(options, i) | |
| 265 | 268 | buffer(offset + j) = options%ignore_prefixes(i)(j:j) |
| 266 | 269 | end do |
| 267 | - buffer(offset + len_trim(options%ignore_prefixes(i)) + 1) = c_null_char | |
| 270 | + buffer(offset + ignore_prefix_length(options, i) + 1) = c_null_char | |
| 268 | 271 | end do |
| 269 | 272 | end subroutine pack_ignore_prefixes |
| 270 | 273 | |
@@ -306,20 +309,22 @@ contains | ||
| 306 | 309 | type(watch_options), intent(in) :: options |
| 307 | 310 | character(len=*), intent(in) :: path |
| 308 | 311 | integer :: i |
| 309 | - character(len=:), allocatable :: prefix | |
| 312 | + integer :: prefix_len | |
| 310 | 313 | |
| 311 | 314 | matches = .false. |
| 312 | 315 | if (.not. allocated(options%ignore_prefixes)) return |
| 313 | 316 | |
| 314 | 317 | do i = 1, size(options%ignore_prefixes) |
| 315 | - prefix = trim(options%ignore_prefixes(i)) | |
| 316 | - if (len(prefix) == 0) cycle | |
| 317 | - if (path == prefix) then | |
| 318 | - matches = .true. | |
| 319 | - return | |
| 318 | + prefix_len = ignore_prefix_length(options, i) | |
| 319 | + if (prefix_len == 0) cycle | |
| 320 | + if (len(path) == prefix_len) then | |
| 321 | + if (path == options%ignore_prefixes(i)(1:prefix_len)) then | |
| 322 | + matches = .true. | |
| 323 | + return | |
| 324 | + end if | |
| 320 | 325 | end if |
| 321 | - if (len(path) > len(prefix)) then | |
| 322 | - if (path(1:len(prefix)) == prefix .and. path(len(prefix) + 1:len(prefix) + 1) == "/") then | |
| 326 | + if (len(path) > prefix_len) then | |
| 327 | + if (path(1:prefix_len) == options%ignore_prefixes(i)(1:prefix_len) .and. path(prefix_len + 1:prefix_len + 1) == "/") then | |
| 323 | 328 | matches = .true. |
| 324 | 329 | return |
| 325 | 330 | end if |
@@ -327,6 +332,17 @@ contains | ||
| 327 | 332 | end do |
| 328 | 333 | end function path_matches_ignore_prefix |
| 329 | 334 | |
| 335 | + integer function ignore_prefix_length(options, index_value) result(length_value) | |
| 336 | + type(watch_options), intent(in) :: options | |
| 337 | + integer, intent(in) :: index_value | |
| 338 | + | |
| 339 | + if (allocated(options%ignore_prefix_lengths)) then | |
| 340 | + length_value = options%ignore_prefix_lengths(index_value) | |
| 341 | + else | |
| 342 | + length_value = len_trim(options%ignore_prefixes(index_value)) | |
| 343 | + end if | |
| 344 | + end function ignore_prefix_length | |
| 345 | + | |
| 330 | 346 | function path_after_root(root, path) result(relative) |
| 331 | 347 | character(len=*), intent(in) :: root |
| 332 | 348 | character(len=*), intent(in) :: path |
@@ -752,105 +768,118 @@ contains | ||
| 752 | 768 | if (previous_entry%is_directory .neqv. current_entry%is_directory) changed = .true. |
| 753 | 769 | end function entry_changed |
| 754 | 770 | |
| 755 | - subroutine parse_snapshot_text(text, entries) | |
| 756 | - character(len=*), intent(in) :: text | |
| 771 | + subroutine parse_snapshot_buffer(buffer, count, entries) | |
| 772 | + character(kind=c_char), intent(in) :: buffer(:) | |
| 773 | + integer, intent(in) :: count | |
| 757 | 774 | type(watch_entry), allocatable, intent(out) :: entries(:) |
| 758 | - integer :: count | |
| 759 | 775 | integer :: i |
| 776 | + integer :: field_count | |
| 777 | + integer :: record_count | |
| 760 | 778 | integer :: start |
| 761 | - integer :: n | |
| 779 | + integer :: terminator_index | |
| 780 | + character(len=:), allocatable :: kind_text | |
| 781 | + character(len=:), allocatable :: inode_text | |
| 782 | + character(len=:), allocatable :: size_text | |
| 783 | + character(len=:), allocatable :: mtime_sec_text | |
| 784 | + character(len=:), allocatable :: mtime_nsec_text | |
| 785 | + character(len=:), allocatable :: path_text | |
| 762 | 786 | |
| 763 | - n = len(text) | |
| 764 | - if (n == 0) then | |
| 787 | + if (count <= 0) then | |
| 765 | 788 | allocate(entries(0)) |
| 766 | 789 | return |
| 767 | 790 | end if |
| 768 | 791 | |
| 769 | - count = 0 | |
| 770 | - do i = 1, n | |
| 771 | - if (text(i:i) == new_line("a")) count = count + 1 | |
| 792 | + field_count = 0 | |
| 793 | + do i = 1, count | |
| 794 | + if (buffer(i) == c_null_char) field_count = field_count + 1 | |
| 772 | 795 | end do |
| 773 | - if (text(n:n) /= new_line("a")) count = count + 1 | |
| 796 | + if (field_count == 0 .or. mod(field_count, 6) /= 0) then | |
| 797 | + allocate(entries(0)) | |
| 798 | + return | |
| 799 | + end if | |
| 774 | 800 | |
| 775 | - allocate(entries(count)) | |
| 776 | - count = 0 | |
| 801 | + record_count = field_count / 6 | |
| 802 | + allocate(entries(record_count)) | |
| 777 | 803 | start = 1 |
| 778 | - do i = 1, n | |
| 779 | - if (text(i:i) /= new_line("a")) cycle | |
| 780 | - count = count + 1 | |
| 781 | - call parse_snapshot_line(text(start:i - 1), entries(count)) | |
| 782 | - start = i + 1 | |
| 804 | + do i = 1, record_count | |
| 805 | + call next_nul_field(buffer, count, start, kind_text, terminator_index) | |
| 806 | + if (terminator_index == 0) exit | |
| 807 | + call next_nul_field(buffer, count, start, inode_text, terminator_index) | |
| 808 | + if (terminator_index == 0) exit | |
| 809 | + call next_nul_field(buffer, count, start, size_text, terminator_index) | |
| 810 | + if (terminator_index == 0) exit | |
| 811 | + call next_nul_field(buffer, count, start, mtime_sec_text, terminator_index) | |
| 812 | + if (terminator_index == 0) exit | |
| 813 | + call next_nul_field(buffer, count, start, mtime_nsec_text, terminator_index) | |
| 814 | + if (terminator_index == 0) exit | |
| 815 | + call next_nul_field(buffer, count, start, path_text, terminator_index) | |
| 816 | + if (terminator_index == 0) exit | |
| 817 | + call parse_snapshot_fields(kind_text, inode_text, size_text, mtime_sec_text, mtime_nsec_text, path_text, entries(i)) | |
| 783 | 818 | end do |
| 784 | - | |
| 785 | - if (start <= n) then | |
| 786 | - count = count + 1 | |
| 787 | - call parse_snapshot_line(text(start:n), entries(count)) | |
| 788 | - end if | |
| 789 | - end subroutine parse_snapshot_text | |
| 790 | - | |
| 791 | - subroutine parse_snapshot_line(line, entry) | |
| 792 | - character(len=*), intent(in) :: line | |
| 819 | + end subroutine parse_snapshot_buffer | |
| 820 | + | |
| 821 | + subroutine parse_snapshot_fields(kind_text, inode_text, size_text, mtime_sec_text, mtime_nsec_text, path_text, entry) | |
| 822 | + character(len=*), intent(in) :: kind_text | |
| 823 | + character(len=*), intent(in) :: inode_text | |
| 824 | + character(len=*), intent(in) :: size_text | |
| 825 | + character(len=*), intent(in) :: mtime_sec_text | |
| 826 | + character(len=*), intent(in) :: mtime_nsec_text | |
| 827 | + character(len=*), intent(in) :: path_text | |
| 793 | 828 | type(watch_entry), intent(out) :: entry |
| 794 | - integer :: tab1 | |
| 795 | - integer :: tab2 | |
| 796 | - integer :: tab3 | |
| 797 | - integer :: tab4 | |
| 798 | - integer :: tab5 | |
| 799 | - | |
| 800 | - tab1 = index(line, achar(9)) | |
| 801 | - tab2 = next_tab(line, tab1 + 1) | |
| 802 | - tab3 = next_tab(line, tab2 + 1) | |
| 803 | - tab4 = next_tab(line, tab3 + 1) | |
| 804 | - tab5 = next_tab(line, tab4 + 1) | |
| 805 | - | |
| 806 | - if (tab1 <= 0 .or. tab2 <= 0 .or. tab3 <= 0 .or. tab4 <= 0 .or. tab5 <= 0) then | |
| 807 | - entry = watch_entry() | |
| 808 | - entry%path = "" | |
| 809 | - return | |
| 810 | - end if | |
| 811 | - | |
| 812 | - entry%is_directory = (line(1:1) == "D") | |
| 813 | - read(line(tab1 + 1:tab2 - 1), *) entry%inode | |
| 814 | - read(line(tab2 + 1:tab3 - 1), *) entry%size | |
| 815 | - read(line(tab3 + 1:tab4 - 1), *) entry%mtime_sec | |
| 816 | - read(line(tab4 + 1:tab5 - 1), *) entry%mtime_nsec | |
| 817 | - entry%path = line(tab5 + 1:) | |
| 818 | - end subroutine parse_snapshot_line | |
| 829 | + integer :: iostat_value | |
| 819 | 830 | |
| 820 | - integer function next_tab(line, start_index) result(position) | |
| 821 | - character(len=*), intent(in) :: line | |
| 822 | - integer, intent(in) :: start_index | |
| 823 | - integer :: offset | |
| 824 | - | |
| 825 | - if (start_index > len(line)) then | |
| 826 | - position = 0 | |
| 831 | + entry = watch_entry() | |
| 832 | + if (len(kind_text) == 0) then | |
| 833 | + entry%path = "" | |
| 827 | 834 | return |
| 828 | 835 | end if |
| 829 | 836 | |
| 830 | - offset = index(line(start_index:), achar(9)) | |
| 831 | - if (offset == 0) then | |
| 832 | - position = 0 | |
| 833 | - else | |
| 834 | - position = start_index + offset - 1 | |
| 835 | - end if | |
| 836 | - end function next_tab | |
| 837 | - | |
| 838 | - function buffer_to_text(buffer, count) result(text) | |
| 837 | + entry%is_directory = (kind_text(1:1) == "D") | |
| 838 | + read(inode_text, *, iostat=iostat_value) entry%inode | |
| 839 | + if (iostat_value /= 0) entry%inode = 0 | |
| 840 | + read(size_text, *, iostat=iostat_value) entry%size | |
| 841 | + if (iostat_value /= 0) entry%size = 0 | |
| 842 | + read(mtime_sec_text, *, iostat=iostat_value) entry%mtime_sec | |
| 843 | + if (iostat_value /= 0) entry%mtime_sec = 0 | |
| 844 | + read(mtime_nsec_text, *, iostat=iostat_value) entry%mtime_nsec | |
| 845 | + if (iostat_value /= 0) entry%mtime_nsec = 0 | |
| 846 | + entry%path = path_text | |
| 847 | + end subroutine parse_snapshot_fields | |
| 848 | + | |
| 849 | + subroutine next_nul_field(buffer, count, start_index, field, terminator_index) | |
| 839 | 850 | character(kind=c_char), intent(in) :: buffer(:) |
| 840 | 851 | integer, intent(in) :: count |
| 841 | - character(len=:), allocatable :: text | |
| 852 | + integer, intent(inout) :: start_index | |
| 853 | + character(len=:), allocatable, intent(out) :: field | |
| 854 | + integer, intent(out) :: terminator_index | |
| 842 | 855 | integer :: i |
| 856 | + integer :: width | |
| 843 | 857 | |
| 844 | - if (count <= 0) then | |
| 845 | - text = "" | |
| 858 | + if (start_index > count) then | |
| 859 | + field = "" | |
| 860 | + terminator_index = 0 | |
| 846 | 861 | return |
| 847 | 862 | end if |
| 848 | 863 | |
| 849 | - allocate(character(len=count) :: text) | |
| 850 | - do i = 1, count | |
| 851 | - text(i:i) = char(iachar(buffer(i))) | |
| 864 | + terminator_index = 0 | |
| 865 | + do i = start_index, count | |
| 866 | + if (buffer(i) == c_null_char) then | |
| 867 | + terminator_index = i | |
| 868 | + exit | |
| 869 | + end if | |
| 870 | + end do | |
| 871 | + if (terminator_index == 0) then | |
| 872 | + field = "" | |
| 873 | + return | |
| 874 | + end if | |
| 875 | + | |
| 876 | + width = terminator_index - start_index | |
| 877 | + allocate(character(len=width) :: field) | |
| 878 | + do i = 1, width | |
| 879 | + field(i:i) = char(iachar(buffer(start_index + i - 1))) | |
| 852 | 880 | end do |
| 853 | - end function buffer_to_text | |
| 881 | + start_index = terminator_index + 1 | |
| 882 | + end subroutine next_nul_field | |
| 854 | 883 | |
| 855 | 884 | function errno_message(prefix, errnum) result(message) |
| 856 | 885 | character(len=*), intent(in) :: prefix |
src/fgof_watch_poll.cmodified@@ -138,12 +138,38 @@ static int fgof_watch_append_text(fgof_watch_buffer *buffer, const char *text, s | ||
| 138 | 138 | return 0; |
| 139 | 139 | } |
| 140 | 140 | |
| 141 | +static int fgof_watch_append_field(fgof_watch_buffer *buffer, const char *text, size_t text_len) { | |
| 142 | + static const char nul = '\0'; | |
| 143 | + int status; | |
| 144 | + | |
| 145 | + status = fgof_watch_append_text(buffer, text, text_len); | |
| 146 | + if (status != 0) { | |
| 147 | + return status; | |
| 148 | + } | |
| 149 | + | |
| 150 | + return fgof_watch_append_text(buffer, &nul, 1); | |
| 151 | +} | |
| 152 | + | |
| 153 | +static int fgof_watch_append_int64_field(fgof_watch_buffer *buffer, long long value) { | |
| 154 | + char text[64]; | |
| 155 | + int text_len; | |
| 156 | + | |
| 157 | + text_len = snprintf(text, sizeof(text), "%lld", value); | |
| 158 | + if (text_len < 0) { | |
| 159 | + return EINVAL; | |
| 160 | + } | |
| 161 | + if ((size_t)text_len >= sizeof(text)) { | |
| 162 | + return EOVERFLOW; | |
| 163 | + } | |
| 164 | + | |
| 165 | + return fgof_watch_append_field(buffer, text, (size_t)text_len); | |
| 166 | +} | |
| 167 | + | |
| 141 | 168 | static int fgof_watch_append_entry(fgof_watch_buffer *buffer, const char *path, const struct stat *st) { |
| 142 | - char line[4096]; | |
| 143 | 169 | char kind; |
| 144 | - int line_len; | |
| 145 | 170 | long long mtime_sec; |
| 146 | 171 | long long mtime_nsec; |
| 172 | + int status; | |
| 147 | 173 | |
| 148 | 174 | if (S_ISDIR(st->st_mode)) { |
| 149 | 175 | kind = 'D'; |
@@ -159,26 +185,28 @@ static int fgof_watch_append_entry(fgof_watch_buffer *buffer, const char *path, | ||
| 159 | 185 | mtime_nsec = (long long)st->st_mtim.tv_nsec; |
| 160 | 186 | #endif |
| 161 | 187 | |
| 162 | - line_len = snprintf( | |
| 163 | - line, | |
| 164 | - sizeof(line), | |
| 165 | - "%c\t%lld\t%lld\t%lld\t%lld\t%s\n", | |
| 166 | - kind, | |
| 167 | - (long long)st->st_ino, | |
| 168 | - (long long)st->st_size, | |
| 169 | - mtime_sec, | |
| 170 | - mtime_nsec, | |
| 171 | - path | |
| 172 | - ); | |
| 173 | - | |
| 174 | - if (line_len < 0) { | |
| 175 | - return EINVAL; | |
| 188 | + status = fgof_watch_append_field(buffer, &kind, 1); | |
| 189 | + if (status != 0) { | |
| 190 | + return status; | |
| 176 | 191 | } |
| 177 | - if ((size_t)line_len >= sizeof(line)) { | |
| 178 | - return EOVERFLOW; | |
| 192 | + status = fgof_watch_append_int64_field(buffer, (long long)st->st_ino); | |
| 193 | + if (status != 0) { | |
| 194 | + return status; | |
| 195 | + } | |
| 196 | + status = fgof_watch_append_int64_field(buffer, (long long)st->st_size); | |
| 197 | + if (status != 0) { | |
| 198 | + return status; | |
| 199 | + } | |
| 200 | + status = fgof_watch_append_int64_field(buffer, mtime_sec); | |
| 201 | + if (status != 0) { | |
| 202 | + return status; | |
| 203 | + } | |
| 204 | + status = fgof_watch_append_int64_field(buffer, mtime_nsec); | |
| 205 | + if (status != 0) { | |
| 206 | + return status; | |
| 179 | 207 | } |
| 180 | 208 | |
| 181 | - return fgof_watch_append_text(buffer, line, (size_t)line_len); | |
| 209 | + return fgof_watch_append_field(buffer, path, strlen(path)); | |
| 182 | 210 | } |
| 183 | 211 | |
| 184 | 212 | static int fgof_watch_visit( |
@@ -195,6 +223,7 @@ static int fgof_watch_visit( | ||
| 195 | 223 | DIR *dirp; |
| 196 | 224 | struct dirent *entry; |
| 197 | 225 | struct stat st; |
| 226 | + int status; | |
| 198 | 227 | |
| 199 | 228 | if (fgof_watch_should_ignore(root, path, ignore_hidden, prefix_count, prefix_stride, prefixes)) { |
| 200 | 229 | return 0; |
@@ -207,8 +236,8 @@ static int fgof_watch_visit( | ||
| 207 | 236 | return errno; |
| 208 | 237 | } |
| 209 | 238 | |
| 210 | - if (fgof_watch_append_entry(buffer, path, &st) != 0) { | |
| 211 | - return -1; | |
| 239 | + if ((status = fgof_watch_append_entry(buffer, path, &st)) != 0) { | |
| 240 | + return status; | |
| 212 | 241 | } |
| 213 | 242 | |
| 214 | 243 | if (!S_ISDIR(st.st_mode)) { |
@@ -230,8 +259,6 @@ static int fgof_watch_visit( | ||
| 230 | 259 | while ((entry = readdir(dirp)) != NULL) { |
| 231 | 260 | char *child; |
| 232 | 261 | size_t child_len; |
| 233 | - int status; | |
| 234 | - | |
| 235 | 262 | if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { |
| 236 | 263 | continue; |
| 237 | 264 | } |
src/fgof_watch_types.f90modified@@ -29,6 +29,7 @@ module fgof_watch_types | ||
| 29 | 29 | logical :: ignore_hidden = .false. |
| 30 | 30 | logical :: emit_directory_events = .true. |
| 31 | 31 | character(len=:), allocatable :: ignore_prefixes(:) |
| 32 | + integer, allocatable :: ignore_prefix_lengths(:) | |
| 32 | 33 | end type watch_options |
| 33 | 34 | |
| 34 | 35 | type :: watch_entry |
test/test_watch_filters.f90modified@@ -7,11 +7,13 @@ program test_watch_filters | ||
| 7 | 7 | expect_single_event, & |
| 8 | 8 | make_dir, & |
| 9 | 9 | remove_tree, & |
| 10 | + touch_path, & | |
| 10 | 11 | write_text |
| 11 | 12 | implicit none |
| 12 | 13 | |
| 13 | 14 | call test_ignore_hidden() |
| 14 | 15 | call test_ignore_prefixes() |
| 16 | + call test_ignore_prefix_literal_spaces() | |
| 15 | 17 | call test_directory_event_shaping() |
| 16 | 18 | |
| 17 | 19 | contains |
@@ -56,7 +58,7 @@ contains | ||
| 56 | 58 | call set_ignore_prefixes(options, [character(len=len(ignored_dir)) :: ignored_dir]) |
| 57 | 59 | call init_watch(session, root, options) |
| 58 | 60 | |
| 59 | - call write_text(ignored_path, "alpha") | |
| 61 | + call touch_path(ignored_path) | |
| 60 | 62 | events = poll_watch(session) |
| 61 | 63 | call expect_no_events(events, "ignored prefix should suppress nested file creation") |
| 62 | 64 | |
@@ -71,6 +73,32 @@ contains | ||
| 71 | 73 | call remove_tree(root) |
| 72 | 74 | end subroutine test_ignore_prefixes |
| 73 | 75 | |
| 76 | + subroutine test_ignore_prefix_literal_spaces() | |
| 77 | + character(len=*), parameter :: root = "build/watch-tests-prefix-literal-spaces" | |
| 78 | + character(len=:), allocatable :: ignored_path | |
| 79 | + character(len=*), parameter :: visible_path = "build/watch-tests-prefix-literal-spaces/main.txt" | |
| 80 | + type(watch_event), allocatable :: events(:) | |
| 81 | + type(watch_options) :: options | |
| 82 | + type(watch_session) :: session | |
| 83 | + | |
| 84 | + call ensure_clean_dir(root) | |
| 85 | + | |
| 86 | + ignored_path = root // "/skip " | |
| 87 | + call set_ignore_prefixes(options, [ignored_path]) | |
| 88 | + call init_watch(session, root, options) | |
| 89 | + | |
| 90 | + call touch_path(ignored_path) | |
| 91 | + events = poll_watch(session) | |
| 92 | + call expect_no_events(events, "ignore prefix should preserve trailing spaces literally") | |
| 93 | + | |
| 94 | + call write_text(visible_path, "beta") | |
| 95 | + events = poll_watch(session) | |
| 96 | + call expect_single_event(events, FGOF_WATCH_EVT_CREATED, visible_path, "", .false., "visible file outside literal-space prefix should still be reported") | |
| 97 | + | |
| 98 | + call reset_watch(session) | |
| 99 | + call remove_tree(root) | |
| 100 | + end subroutine test_ignore_prefix_literal_spaces | |
| 101 | + | |
| 74 | 102 | subroutine test_directory_event_shaping() |
| 75 | 103 | character(len=*), parameter :: root = "build/watch-tests-directory-events" |
| 76 | 104 | character(len=*), parameter :: child_dir = "build/watch-tests-directory-events/child" |
test/test_watch_literal_paths.f90added@@ -0,0 +1,47 @@ | ||
| 1 | +program test_watch_literal_paths | |
| 2 | + use fgof_watch, only : init_watch, poll_watch, reset_watch | |
| 3 | + use fgof_watch_types, only : FGOF_WATCH_EVT_CREATED, watch_event, watch_session | |
| 4 | + use watch_test_support, only : ensure_clean_dir, expect_single_event, remove_tree, write_text | |
| 5 | + implicit none | |
| 6 | + | |
| 7 | + call test_tab_name() | |
| 8 | + call test_newline_name() | |
| 9 | + | |
| 10 | +contains | |
| 11 | + | |
| 12 | + subroutine test_tab_name() | |
| 13 | + character(len=*), parameter :: root = "build/watch-tests-tab-name" | |
| 14 | + character(len=:), allocatable :: tab_path | |
| 15 | + type(watch_event), allocatable :: events(:) | |
| 16 | + type(watch_session) :: session | |
| 17 | + | |
| 18 | + call ensure_clean_dir(root) | |
| 19 | + call init_watch(session, root) | |
| 20 | + | |
| 21 | + tab_path = root // "/tab" // achar(9) // "name.txt" | |
| 22 | + call write_text(tab_path, "alpha") | |
| 23 | + events = poll_watch(session) | |
| 24 | + call expect_single_event(events, FGOF_WATCH_EVT_CREATED, tab_path, "", .false., "tab characters in paths should round-trip through watch events") | |
| 25 | + | |
| 26 | + call reset_watch(session) | |
| 27 | + call remove_tree(root) | |
| 28 | + end subroutine test_tab_name | |
| 29 | + | |
| 30 | + subroutine test_newline_name() | |
| 31 | + character(len=*), parameter :: root = "build/watch-tests-newline-name" | |
| 32 | + character(len=:), allocatable :: newline_path | |
| 33 | + type(watch_event), allocatable :: events(:) | |
| 34 | + type(watch_session) :: session | |
| 35 | + | |
| 36 | + call ensure_clean_dir(root) | |
| 37 | + call init_watch(session, root) | |
| 38 | + | |
| 39 | + newline_path = root // "/line" // new_line("a") // "break.txt" | |
| 40 | + call write_text(newline_path, "alpha") | |
| 41 | + events = poll_watch(session) | |
| 42 | + call expect_single_event(events, FGOF_WATCH_EVT_CREATED, newline_path, "", .false., "newline characters in paths should round-trip through watch events") | |
| 43 | + | |
| 44 | + call reset_watch(session) | |
| 45 | + call remove_tree(root) | |
| 46 | + end subroutine test_newline_name | |
| 47 | +end program test_watch_literal_paths | |
test/watch_test_support.f90modified@@ -12,6 +12,7 @@ module watch_test_support | ||
| 12 | 12 | public :: move_path |
| 13 | 13 | public :: remove_path |
| 14 | 14 | public :: remove_tree |
| 15 | + public :: touch_path | |
| 15 | 16 | public :: write_text |
| 16 | 17 | |
| 17 | 18 | contains |
@@ -48,6 +49,12 @@ contains | ||
| 48 | 49 | call run_command("rm -f " // path) |
| 49 | 50 | end subroutine remove_path |
| 50 | 51 | |
| 52 | + subroutine touch_path(path) | |
| 53 | + character(len=*), intent(in) :: path | |
| 54 | + | |
| 55 | + call run_command("touch " // shell_quote(path)) | |
| 56 | + end subroutine touch_path | |
| 57 | + | |
| 51 | 58 | subroutine move_path(source, destination) |
| 52 | 59 | character(len=*), intent(in) :: source |
| 53 | 60 | character(len=*), intent(in) :: destination |
@@ -105,4 +112,20 @@ contains | ||
| 105 | 112 | if (exitstat /= 0) error stop "command failed: " // trim(command) |
| 106 | 113 | end subroutine run_command |
| 107 | 114 | |
| 115 | + function shell_quote(text) result(quoted) | |
| 116 | + character(len=*), intent(in) :: text | |
| 117 | + character(len=:), allocatable :: quoted | |
| 118 | + integer :: i | |
| 119 | + | |
| 120 | + quoted = "'" | |
| 121 | + do i = 1, len(text) | |
| 122 | + if (text(i:i) == "'") then | |
| 123 | + quoted = quoted // achar(39) // achar(34) // achar(39) // achar(34) // achar(39) | |
| 124 | + else | |
| 125 | + quoted = quoted // text(i:i) | |
| 126 | + end if | |
| 127 | + end do | |
| 128 | + quoted = quoted // "'" | |
| 129 | + end function shell_quote | |
| 130 | + | |
| 108 | 131 | end module watch_test_support |