1use crate::domain::Domain;
7use crate::error::{KernelError, Result};
8use crate::kernel::{KernelMetadata, KernelMode};
9use crate::license::{LicenseError, LicenseValidator, SharedLicenseValidator};
10use crate::traits::{BatchKernel, BatchKernelDyn, GpuKernel, RingKernelDyn, TypeErasedBatchKernel};
11use hashbrown::HashMap;
12use std::sync::{Arc, RwLock};
13use tracing::{debug, info, warn};
14
15#[derive(Debug, Clone, Default)]
17pub struct RegistryStats {
18 pub total: usize,
20 pub batch_kernels: usize,
22 pub ring_kernels: usize,
24 pub by_domain: HashMap<Domain, usize>,
26}
27
28#[derive(Clone)]
30pub struct BatchKernelEntry {
31 pub metadata: KernelMetadata,
33 factory: Arc<dyn Fn() -> Arc<dyn BatchKernelDyn> + Send + Sync>,
35}
36
37impl BatchKernelEntry {
38 pub fn new<F>(metadata: KernelMetadata, factory: F) -> Self
40 where
41 F: Fn() -> Arc<dyn BatchKernelDyn> + Send + Sync + 'static,
42 {
43 Self {
44 metadata,
45 factory: Arc::new(factory),
46 }
47 }
48
49 #[must_use]
51 pub fn create(&self) -> Arc<dyn BatchKernelDyn> {
52 (self.factory)()
53 }
54}
55
56impl std::fmt::Debug for BatchKernelEntry {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct("BatchKernelEntry")
59 .field("metadata", &self.metadata)
60 .finish()
61 }
62}
63
64#[derive(Clone)]
66pub struct RingKernelEntry {
67 pub metadata: KernelMetadata,
69 factory: Arc<dyn Fn() -> Arc<dyn RingKernelDyn> + Send + Sync>,
71}
72
73impl RingKernelEntry {
74 pub fn new<F>(metadata: KernelMetadata, factory: F) -> Self
76 where
77 F: Fn() -> Arc<dyn RingKernelDyn> + Send + Sync + 'static,
78 {
79 Self {
80 metadata,
81 factory: Arc::new(factory),
82 }
83 }
84
85 #[must_use]
87 pub fn create(&self) -> Arc<dyn RingKernelDyn> {
88 (self.factory)()
89 }
90}
91
92impl std::fmt::Debug for RingKernelEntry {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 f.debug_struct("RingKernelEntry")
95 .field("metadata", &self.metadata)
96 .finish()
97 }
98}
99
100#[derive(Debug)]
102pub struct KernelRegistry {
103 batch_kernels: RwLock<HashMap<String, BatchKernelEntry>>,
105 ring_kernels: RwLock<HashMap<String, RingKernelEntry>>,
107 metadata_only: RwLock<HashMap<String, KernelMetadata>>,
109 license: Option<SharedLicenseValidator>,
111}
112
113impl KernelRegistry {
114 #[must_use]
116 pub fn new() -> Self {
117 Self {
118 batch_kernels: RwLock::new(HashMap::new()),
119 ring_kernels: RwLock::new(HashMap::new()),
120 metadata_only: RwLock::new(HashMap::new()),
121 license: None,
122 }
123 }
124
125 #[must_use]
127 pub fn with_license(license: SharedLicenseValidator) -> Self {
128 Self {
129 batch_kernels: RwLock::new(HashMap::new()),
130 ring_kernels: RwLock::new(HashMap::new()),
131 metadata_only: RwLock::new(HashMap::new()),
132 license: Some(license),
133 }
134 }
135
136 pub fn set_license(&mut self, license: SharedLicenseValidator) {
138 self.license = Some(license);
139 }
140
141 pub fn register_batch(&self, entry: BatchKernelEntry) -> Result<()> {
143 if let Some(ref license) = self.license {
145 self.validate_kernel_license(license.as_ref(), &entry.metadata)?;
146 }
147
148 let id = entry.metadata.id.clone();
149 let mut kernels = self.batch_kernels.write().unwrap();
150
151 if kernels.contains_key(&id) {
152 return Err(KernelError::KernelAlreadyRegistered(id));
153 }
154
155 debug!(kernel_id = %id, domain = %entry.metadata.domain, "Registering batch kernel");
156 kernels.insert(id, entry);
157 Ok(())
158 }
159
160 pub fn register_ring(&self, entry: RingKernelEntry) -> Result<()> {
162 if let Some(ref license) = self.license {
164 self.validate_kernel_license(license.as_ref(), &entry.metadata)?;
165 }
166
167 let id = entry.metadata.id.clone();
168 let mut kernels = self.ring_kernels.write().unwrap();
169
170 if kernels.contains_key(&id) {
171 return Err(KernelError::KernelAlreadyRegistered(id));
172 }
173
174 debug!(kernel_id = %id, domain = %entry.metadata.domain, "Registering ring kernel");
175 kernels.insert(id, entry);
176 Ok(())
177 }
178
179 pub fn register_metadata(&self, metadata: KernelMetadata) -> Result<()> {
185 if let Some(ref license) = self.license {
187 self.validate_kernel_license(license.as_ref(), &metadata)?;
188 }
189
190 let id = metadata.id.clone();
191
192 {
194 let batch = self.batch_kernels.read().unwrap();
195 if batch.contains_key(&id) {
196 return Err(KernelError::KernelAlreadyRegistered(id));
197 }
198 }
199 {
200 let ring = self.ring_kernels.read().unwrap();
201 if ring.contains_key(&id) {
202 return Err(KernelError::KernelAlreadyRegistered(id));
203 }
204 }
205
206 let mut metadata_map = self.metadata_only.write().unwrap();
207 if metadata_map.contains_key(&id) {
208 return Err(KernelError::KernelAlreadyRegistered(id));
209 }
210
211 debug!(kernel_id = %id, domain = %metadata.domain, mode = ?metadata.mode, "Registering kernel metadata");
212 metadata_map.insert(id, metadata);
213 Ok(())
214 }
215
216 fn validate_kernel_license(
218 &self,
219 license: &dyn LicenseValidator,
220 metadata: &KernelMetadata,
221 ) -> Result<()> {
222 license
224 .validate_domain(metadata.domain)
225 .map_err(KernelError::from)?;
226
227 if metadata.requires_gpu_native && !license.gpu_native_enabled() {
229 return Err(KernelError::from(LicenseError::GpuNativeNotLicensed));
230 }
231
232 Ok(())
233 }
234
235 #[must_use]
237 pub fn get_batch(&self, id: &str) -> Option<BatchKernelEntry> {
238 let kernels = self.batch_kernels.read().unwrap();
239 kernels.get(id).cloned()
240 }
241
242 #[must_use]
244 pub fn get_ring(&self, id: &str) -> Option<RingKernelEntry> {
245 let kernels = self.ring_kernels.read().unwrap();
246 kernels.get(id).cloned()
247 }
248
249 #[must_use]
251 pub fn get(&self, id: &str) -> Option<KernelMetadata> {
252 if let Some(entry) = self.get_batch(id) {
253 return Some(entry.metadata);
254 }
255 if let Some(entry) = self.get_ring(id) {
256 return Some(entry.metadata);
257 }
258 let metadata_map = self.metadata_only.read().unwrap();
259 metadata_map.get(id).cloned()
260 }
261
262 #[must_use]
264 pub fn get_metadata_only(&self, id: &str) -> Option<KernelMetadata> {
265 let metadata_map = self.metadata_only.read().unwrap();
266 metadata_map.get(id).cloned()
267 }
268
269 #[must_use]
271 pub fn contains(&self, id: &str) -> bool {
272 let batch = self.batch_kernels.read().unwrap();
273 let ring = self.ring_kernels.read().unwrap();
274 let metadata = self.metadata_only.read().unwrap();
275 batch.contains_key(id) || ring.contains_key(id) || metadata.contains_key(id)
276 }
277
278 #[must_use]
280 pub fn batch_kernel_ids(&self) -> Vec<String> {
281 let kernels = self.batch_kernels.read().unwrap();
282 kernels.keys().cloned().collect()
283 }
284
285 #[must_use]
287 pub fn ring_kernel_ids(&self) -> Vec<String> {
288 let kernels = self.ring_kernels.read().unwrap();
289 kernels.keys().cloned().collect()
290 }
291
292 #[must_use]
294 pub fn metadata_only_ids(&self) -> Vec<String> {
295 let metadata = self.metadata_only.read().unwrap();
296 metadata.keys().cloned().collect()
297 }
298
299 #[must_use]
301 pub fn all_kernel_ids(&self) -> Vec<String> {
302 let mut ids = self.batch_kernel_ids();
303 ids.extend(self.ring_kernel_ids());
304 ids.extend(self.metadata_only_ids());
305 ids
306 }
307
308 #[must_use]
310 pub fn by_domain(&self, domain: Domain) -> Vec<KernelMetadata> {
311 let mut result = Vec::new();
312
313 let batch = self.batch_kernels.read().unwrap();
314 for entry in batch.values() {
315 if entry.metadata.domain == domain {
316 result.push(entry.metadata.clone());
317 }
318 }
319
320 let ring = self.ring_kernels.read().unwrap();
321 for entry in ring.values() {
322 if entry.metadata.domain == domain {
323 result.push(entry.metadata.clone());
324 }
325 }
326
327 let metadata = self.metadata_only.read().unwrap();
328 for entry in metadata.values() {
329 if entry.domain == domain {
330 result.push(entry.clone());
331 }
332 }
333
334 result
335 }
336
337 #[must_use]
339 pub fn by_mode(&self, mode: KernelMode) -> Vec<KernelMetadata> {
340 let mut result: Vec<KernelMetadata> = match mode {
341 KernelMode::Batch => {
342 let kernels = self.batch_kernels.read().unwrap();
343 kernels.values().map(|e| e.metadata.clone()).collect()
344 }
345 KernelMode::Ring => {
346 let kernels = self.ring_kernels.read().unwrap();
347 kernels.values().map(|e| e.metadata.clone()).collect()
348 }
349 };
350
351 let metadata = self.metadata_only.read().unwrap();
353 for entry in metadata.values() {
354 if entry.mode == mode {
355 result.push(entry.clone());
356 }
357 }
358
359 result
360 }
361
362 #[must_use]
364 pub fn stats(&self) -> RegistryStats {
365 let batch = self.batch_kernels.read().unwrap();
366 let ring = self.ring_kernels.read().unwrap();
367 let metadata = self.metadata_only.read().unwrap();
368
369 let mut by_domain: HashMap<Domain, usize> = HashMap::new();
370
371 for entry in batch.values() {
372 *by_domain.entry(entry.metadata.domain).or_default() += 1;
373 }
374
375 for entry in ring.values() {
376 *by_domain.entry(entry.metadata.domain).or_default() += 1;
377 }
378
379 let mut metadata_batch = 0;
381 let mut metadata_ring = 0;
382 for entry in metadata.values() {
383 *by_domain.entry(entry.domain).or_default() += 1;
384 match entry.mode {
385 KernelMode::Batch => metadata_batch += 1,
386 KernelMode::Ring => metadata_ring += 1,
387 }
388 }
389
390 RegistryStats {
391 total: batch.len() + ring.len() + metadata.len(),
392 batch_kernels: batch.len() + metadata_batch,
393 ring_kernels: ring.len() + metadata_ring,
394 by_domain,
395 }
396 }
397
398 pub fn register_batch_typed<K, I, O>(
417 &self,
418 factory: impl Fn() -> K + Send + Sync + 'static,
419 ) -> Result<()>
420 where
421 K: BatchKernel<I, O> + 'static,
422 I: serde::de::DeserializeOwned + Send + Sync + 'static,
423 O: serde::Serialize + Send + Sync + 'static,
424 {
425 let sample = factory();
426 let metadata = sample.metadata().clone();
427 drop(sample);
428 let entry = BatchKernelEntry::new(metadata, move || {
429 Arc::new(TypeErasedBatchKernel::new(factory()))
430 });
431 self.register_batch(entry)
432 }
433
434 pub fn register_batch_metadata_from<K>(&self, factory: impl Fn() -> K) -> Result<()>
444 where
445 K: GpuKernel,
446 {
447 let sample = factory();
448 let metadata = sample.metadata().clone();
449 self.register_metadata(metadata)
450 }
451
452 pub fn register_ring_metadata_from<K>(&self, factory: impl Fn() -> K) -> Result<()>
464 where
465 K: GpuKernel,
466 {
467 let sample = factory();
468 let metadata = sample.metadata().clone();
469 self.register_metadata(metadata)
470 }
471
472 pub async fn execute_batch(&self, kernel_id: &str, input_json: &[u8]) -> Result<Vec<u8>> {
482 let entry = self
483 .get_batch(kernel_id)
484 .ok_or_else(|| KernelError::KernelNotFound(kernel_id.to_string()))?;
485 let kernel = entry.create();
486 kernel.execute_dyn(input_json).await
487 }
488
489 #[must_use]
491 pub fn total_count(&self) -> usize {
492 let batch = self.batch_kernels.read().unwrap();
493 let ring = self.ring_kernels.read().unwrap();
494 let metadata = self.metadata_only.read().unwrap();
495 batch.len() + ring.len() + metadata.len()
496 }
497
498 #[must_use]
502 pub fn all_metadata(&self) -> Vec<KernelMetadata> {
503 let mut result = Vec::new();
504
505 let batch = self.batch_kernels.read().unwrap();
506 for entry in batch.values() {
507 result.push(entry.metadata.clone());
508 }
509
510 let ring = self.ring_kernels.read().unwrap();
511 for entry in ring.values() {
512 result.push(entry.metadata.clone());
513 }
514
515 let metadata = self.metadata_only.read().unwrap();
516 for entry in metadata.values() {
517 result.push(entry.clone());
518 }
519
520 result.sort_by(|a, b| a.id.cmp(&b.id));
521 result
522 }
523
524 #[must_use]
526 pub fn search(&self, pattern: &str) -> Vec<KernelMetadata> {
527 let pattern_lower = pattern.to_lowercase();
528 self.all_metadata()
529 .into_iter()
530 .filter(|m| {
531 m.id.to_lowercase().contains(&pattern_lower)
532 || m.description.to_lowercase().contains(&pattern_lower)
533 })
534 .collect()
535 }
536
537 #[must_use]
542 pub fn executable_kernel_ids(&self) -> Vec<String> {
543 self.batch_kernel_ids()
544 }
545
546 #[must_use]
548 pub fn is_executable(&self, id: &str) -> bool {
549 let batch = self.batch_kernels.read().unwrap();
550 batch.contains_key(id)
551 }
552
553 pub fn clear(&self) {
555 let mut batch = self.batch_kernels.write().unwrap();
556 let mut ring = self.ring_kernels.write().unwrap();
557 let mut metadata = self.metadata_only.write().unwrap();
558 batch.clear();
559 ring.clear();
560 metadata.clear();
561 info!("Cleared kernel registry");
562 }
563
564 pub fn unregister(&self, id: &str) -> bool {
566 let mut batch = self.batch_kernels.write().unwrap();
567 if batch.remove(id).is_some() {
568 debug!(kernel_id = %id, "Unregistered batch kernel");
569 return true;
570 }
571
572 let mut ring = self.ring_kernels.write().unwrap();
573 if ring.remove(id).is_some() {
574 debug!(kernel_id = %id, "Unregistered ring kernel");
575 return true;
576 }
577
578 let mut metadata = self.metadata_only.write().unwrap();
579 if metadata.remove(id).is_some() {
580 debug!(kernel_id = %id, "Unregistered metadata-only kernel");
581 return true;
582 }
583
584 warn!(kernel_id = %id, "Attempted to unregister non-existent kernel");
585 false
586 }
587}
588
589impl Default for KernelRegistry {
590 fn default() -> Self {
591 Self::new()
592 }
593}
594
595#[derive(Default)]
597pub struct KernelRegistryBuilder {
598 license: Option<SharedLicenseValidator>,
599 batch_entries: Vec<BatchKernelEntry>,
600 ring_entries: Vec<RingKernelEntry>,
601}
602
603impl KernelRegistryBuilder {
604 #[must_use]
606 pub fn new() -> Self {
607 Self::default()
608 }
609
610 #[must_use]
612 pub fn with_license(mut self, license: SharedLicenseValidator) -> Self {
613 self.license = Some(license);
614 self
615 }
616
617 #[must_use]
619 pub fn with_batch(mut self, entry: BatchKernelEntry) -> Self {
620 self.batch_entries.push(entry);
621 self
622 }
623
624 #[must_use]
626 pub fn with_ring(mut self, entry: RingKernelEntry) -> Self {
627 self.ring_entries.push(entry);
628 self
629 }
630
631 pub fn build(self) -> Result<KernelRegistry> {
637 let registry = match self.license {
638 Some(license) => KernelRegistry::with_license(license),
639 None => KernelRegistry::new(),
640 };
641
642 for entry in self.batch_entries {
643 registry.register_batch(entry)?;
644 }
645
646 for entry in self.ring_entries {
647 registry.register_ring(entry)?;
648 }
649
650 info!(
651 total = registry.total_count(),
652 batch = registry.batch_kernel_ids().len(),
653 ring = registry.ring_kernel_ids().len(),
654 "Built kernel registry"
655 );
656
657 Ok(registry)
658 }
659}
660
661static GLOBAL_REGISTRY: std::sync::OnceLock<KernelRegistry> = std::sync::OnceLock::new();
665
666pub fn global_registry() -> &'static KernelRegistry {
668 GLOBAL_REGISTRY.get_or_init(KernelRegistry::new)
669}
670
671pub fn init_global_registry(license: SharedLicenseValidator) -> &'static KernelRegistry {
679 let registry = KernelRegistry::with_license(license);
680 GLOBAL_REGISTRY
681 .set(registry)
682 .expect("Global registry already initialized");
683 GLOBAL_REGISTRY.get().unwrap()
684}
685
686#[cfg(test)]
687mod tests {
688 use super::*;
689 use crate::license::DevelopmentLicense;
690
691 fn test_batch_entry() -> BatchKernelEntry {
692 let metadata = KernelMetadata::batch("test-batch", Domain::Core);
693 BatchKernelEntry::new(metadata, || {
694 panic!("Not implemented for tests")
696 })
697 }
698
699 fn test_ring_entry() -> RingKernelEntry {
700 let metadata = KernelMetadata::ring("test-ring", Domain::Core);
701 RingKernelEntry::new(metadata, || {
702 panic!("Not implemented for tests")
704 })
705 }
706
707 #[test]
708 fn test_registry_creation() {
709 let registry = KernelRegistry::new();
710 assert_eq!(registry.total_count(), 0);
711 }
712
713 #[test]
714 fn test_batch_registration() {
715 let registry = KernelRegistry::new();
716 let entry = test_batch_entry();
717
718 registry.register_batch(entry).unwrap();
719 assert_eq!(registry.total_count(), 1);
720 assert!(registry.contains("test-batch"));
721 assert!(registry.get_batch("test-batch").is_some());
722 }
723
724 #[test]
725 fn test_ring_registration() {
726 let registry = KernelRegistry::new();
727 let entry = test_ring_entry();
728
729 registry.register_ring(entry).unwrap();
730 assert_eq!(registry.total_count(), 1);
731 assert!(registry.contains("test-ring"));
732 assert!(registry.get_ring("test-ring").is_some());
733 }
734
735 #[test]
736 fn test_duplicate_registration() {
737 let registry = KernelRegistry::new();
738 let entry1 = test_batch_entry();
739 let entry2 = test_batch_entry();
740
741 registry.register_batch(entry1).unwrap();
742 let result = registry.register_batch(entry2);
743 assert!(result.is_err());
744 }
745
746 #[test]
747 fn test_by_domain() {
748 let registry = KernelRegistry::new();
749
750 let core_entry = test_batch_entry();
751 registry.register_batch(core_entry).unwrap();
752
753 let graph_entry = BatchKernelEntry::new(
754 KernelMetadata::batch("test-graph", Domain::GraphAnalytics),
755 || panic!("Not implemented"),
756 );
757 registry.register_batch(graph_entry).unwrap();
758
759 let core_kernels = registry.by_domain(Domain::Core);
760 assert_eq!(core_kernels.len(), 1);
761
762 let graph_kernels = registry.by_domain(Domain::GraphAnalytics);
763 assert_eq!(graph_kernels.len(), 1);
764 }
765
766 #[test]
767 fn test_stats() {
768 let registry = KernelRegistry::new();
769
770 registry.register_batch(test_batch_entry()).unwrap();
771 registry.register_ring(test_ring_entry()).unwrap();
772
773 let stats = registry.stats();
774 assert_eq!(stats.total, 2);
775 assert_eq!(stats.batch_kernels, 1);
776 assert_eq!(stats.ring_kernels, 1);
777 assert_eq!(stats.by_domain.get(&Domain::Core), Some(&2));
778 }
779
780 #[test]
781 fn test_unregister() {
782 let registry = KernelRegistry::new();
783 registry.register_batch(test_batch_entry()).unwrap();
784
785 assert!(registry.contains("test-batch"));
786 assert!(registry.unregister("test-batch"));
787 assert!(!registry.contains("test-batch"));
788 assert!(!registry.unregister("test-batch"));
789 }
790
791 #[test]
792 fn test_with_license() {
793 let license: SharedLicenseValidator = Arc::new(DevelopmentLicense);
794 let registry = KernelRegistry::with_license(license);
795
796 registry.register_batch(test_batch_entry()).unwrap();
798 registry.register_ring(test_ring_entry()).unwrap();
799 }
800
801 #[test]
802 fn test_builder() {
803 let registry = KernelRegistryBuilder::new()
804 .with_batch(test_batch_entry())
805 .with_ring(test_ring_entry())
806 .build()
807 .unwrap();
808
809 assert_eq!(registry.total_count(), 2);
810 }
811
812 #[test]
813 fn test_metadata_only_registration() {
814 let registry = KernelRegistry::new();
815 let metadata = KernelMetadata::batch("test-metadata", Domain::GraphAnalytics);
816
817 registry.register_metadata(metadata).unwrap();
818
819 assert_eq!(registry.total_count(), 1);
820 assert!(registry.contains("test-metadata"));
821 assert!(registry.get("test-metadata").is_some());
822 assert!(registry.get_metadata_only("test-metadata").is_some());
823 assert!(registry.get_batch("test-metadata").is_none()); }
825
826 #[test]
827 fn test_metadata_only_duplicate() {
828 let registry = KernelRegistry::new();
829 let metadata1 = KernelMetadata::batch("test-dup", Domain::Core);
830 let metadata2 = KernelMetadata::batch("test-dup", Domain::Core);
831
832 registry.register_metadata(metadata1).unwrap();
833 let result = registry.register_metadata(metadata2);
834 assert!(result.is_err());
835 }
836
837 #[test]
838 fn test_metadata_only_conflict_with_batch() {
839 let registry = KernelRegistry::new();
840 registry.register_batch(test_batch_entry()).unwrap();
841
842 let metadata = KernelMetadata::batch("test-batch", Domain::Core);
844 let result = registry.register_metadata(metadata);
845 assert!(result.is_err());
846 }
847
848 #[test]
849 fn test_metadata_only_in_stats() {
850 let registry = KernelRegistry::new();
851
852 let batch_meta = KernelMetadata::batch("meta-batch", Domain::GraphAnalytics);
853 let ring_meta = KernelMetadata::ring("meta-ring", Domain::GraphAnalytics);
854 registry.register_metadata(batch_meta).unwrap();
855 registry.register_metadata(ring_meta).unwrap();
856
857 let stats = registry.stats();
858 assert_eq!(stats.total, 2);
859 assert_eq!(stats.batch_kernels, 1);
860 assert_eq!(stats.ring_kernels, 1);
861 assert_eq!(stats.by_domain.get(&Domain::GraphAnalytics), Some(&2));
862 }
863
864 #[test]
865 fn test_metadata_only_by_domain() {
866 let registry = KernelRegistry::new();
867
868 let graph_meta = KernelMetadata::batch("graph-kernel", Domain::GraphAnalytics);
869 let ml_meta = KernelMetadata::batch("ml-kernel", Domain::StatisticalML);
870 registry.register_metadata(graph_meta).unwrap();
871 registry.register_metadata(ml_meta).unwrap();
872
873 let graph_kernels = registry.by_domain(Domain::GraphAnalytics);
874 assert_eq!(graph_kernels.len(), 1);
875 assert_eq!(graph_kernels[0].id, "graph-kernel");
876
877 let ml_kernels = registry.by_domain(Domain::StatisticalML);
878 assert_eq!(ml_kernels.len(), 1);
879 assert_eq!(ml_kernels[0].id, "ml-kernel");
880 }
881
882 #[test]
883 fn test_unregister_metadata_only() {
884 let registry = KernelRegistry::new();
885 let metadata = KernelMetadata::batch("test-unreg", Domain::Core);
886
887 registry.register_metadata(metadata).unwrap();
888 assert!(registry.contains("test-unreg"));
889
890 assert!(registry.unregister("test-unreg"));
891 assert!(!registry.contains("test-unreg"));
892 }
893}